Skip to content

Commit 1ba797b

Browse files
committed
feat(serve): add --static dir to serve static files
1 parent 11db4a5 commit 1ba797b

File tree

5 files changed

+72
-25
lines changed

5 files changed

+72
-25
lines changed

cli/command.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,15 @@ func buildServe(cfg cliConfig) *cobra.Command {
334334
port := options.NewPortOption()
335335
statusFlagName := "show-status"
336336
listFlagName := "list"
337+
staticFlagName := "static"
337338

338339
c := &cobra.Command{
339340
Use: "serve",
340341
Short: "Starts an HTTP server on localhost",
341-
Long: `Starts an HTTP server on localhost.
342-
Useful for local testing and debugging.`,
342+
Long: `Starts an HTTP server on localhost. Useful for local testing.
343+
344+
If --static DIR is provided the server hosts the files
345+
in that directory. Otherwise the default API is started (use --list to see endoints).`,
343346
Run: func(cmd *cobra.Command, args []string) {
344347
flags := cmd.Flags()
345348

@@ -350,10 +353,12 @@ Useful for local testing and debugging.`,
350353
}
351354

352355
showStatus, _ := flags.GetBool(statusFlagName)
356+
staticRoot, _ := flags.GetString(staticFlagName)
353357

354358
opts := server.Options{
355359
Port: port.Value(),
356360
ShowStatus: showStatus,
361+
StaticRoot: staticRoot,
357362
}
358363

359364
server := server.New(opts)
@@ -380,6 +385,7 @@ Useful for local testing and debugging.`,
380385
}
381386

382387
c.Flags().VarP(port, "port", "p", "Port to listen on. Must be valid number in the range 1024-65535.")
388+
c.Flags().String(staticFlagName, "", "Serve static files from this directory.")
383389
c.Flags().Bool(statusFlagName, false, "Shows current status instead of showing each request.")
384390
c.Flags().Bool(listFlagName, false, "List predefined routes.")
385391
return c

cli/formatter.go

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010

1111
"github.com/lunjon/http/internal/client"
1212
"github.com/lunjon/http/internal/history"
13-
"github.com/lunjon/http/internal/style"
14-
"github.com/lunjon/http/internal/types"
1513
)
1614

1715
var ResponseComponents = []string{"status", "headers", "body"}
@@ -78,23 +76,11 @@ func readBody(r *http.Response) ([]byte, error) {
7876
return buf.Bytes(), err
7977
}
8078

81-
func (f *textFormatter) addHeaders(w io.Writer, r *http.Response) {
82-
taber := types.NewTaber("")
83-
for name, value := range headerToMap(r.Header) {
84-
n := fmt.Sprintf("%s:", name)
85-
v := fmt.Sprint(value)
86-
taber.WriteLine(style.Bold.Render(n), v)
87-
}
88-
fmt.Fprint(w, taber.String())
89-
}
90-
9179
func (f *textFormatter) FormatHistory([]history.Entry) ([]byte, error) {
9280
return nil, nil
9381
}
9482

95-
type jsonFormatter struct {
96-
components []string
97-
}
83+
type jsonFormatter struct{}
9884

9985
func (f *jsonFormatter) FormatResponse(r *http.Response) ([]byte, error) {
10086
output := struct {

flake.nix

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,19 @@
2424
vendorHash = "sha256-h27uHmOQMECkGHFsDggGfm+hRohTVYIkvF7zWFdwlTM=";
2525
doCheck = false;
2626
};
27+
devShells.default = pkgs.mkShell {
28+
name = "http";
29+
shellHook = ''
30+
exec nu
31+
'';
32+
packages = builtins.attrValues {
33+
inherit (pkgs)
34+
go
35+
gopls
36+
go-tools
37+
;
38+
};
39+
};
2740
}
2841
);
2942
}

internal/server/handler.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package server
22

33
import (
4+
"math/rand"
45
"net/http"
56
"strconv"
7+
"strings"
68
"time"
79
)
810

@@ -27,11 +29,18 @@ func (h *requestHandler) handleWithCode(w http.ResponseWriter, r *http.Request)
2729
h.ch <- r
2830

2931
code := r.PathValue("code")
30-
status := http.StatusOK
31-
if code != "" {
32+
var status int
33+
if strings.ToLower(code) == "random" {
34+
// Get a random status code
35+
status = statuses[rnd.Intn(len(statuses))]
36+
} else if code != "" {
3237
if n, err := strconv.Atoi(code); err == nil {
3338
status = n
39+
} else {
40+
status = http.StatusTeapot
3441
}
42+
} else {
43+
status = http.StatusNotFound
3544
}
3645

3746
w.WriteHeader(status)
@@ -41,3 +50,27 @@ func (h *requestHandler) handleTimeout(w http.ResponseWriter, r *http.Request) {
4150
h.ch <- r
4251
time.Sleep(time.Minute * 5)
4352
}
53+
54+
var (
55+
rnd = rand.New(rand.NewSource(time.Now().Unix()))
56+
statuses = []int{
57+
// 2XX
58+
http.StatusOK,
59+
http.StatusCreated,
60+
http.StatusAccepted,
61+
http.StatusNoContent,
62+
// 4XX
63+
http.StatusUnauthorized,
64+
http.StatusPaymentRequired,
65+
http.StatusForbidden,
66+
http.StatusNotFound,
67+
http.StatusMethodNotAllowed,
68+
http.StatusConflict,
69+
http.StatusTeapot,
70+
// 5XX
71+
http.StatusInternalServerError,
72+
http.StatusNotImplemented,
73+
http.StatusServiceUnavailable,
74+
http.StatusGatewayTimeout,
75+
}
76+
)

internal/server/server.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
type Options struct {
1111
Port uint
1212
ShowStatus bool
13+
StaticRoot string
1314
}
1415

1516
type Server struct {
@@ -26,9 +27,16 @@ func New(opts Options) *Server {
2627

2728
handler := newHandler(ch)
2829
mux := http.NewServeMux()
29-
mux.HandleFunc("/~/with-status/{code}", handler.handleWithCode)
30-
mux.HandleFunc("/~/timeout", handler.handleTimeout)
31-
mux.HandleFunc("/", handler.handleDefault)
30+
31+
if opts.StaticRoot != "" {
32+
root := http.Dir(opts.StaticRoot)
33+
fs := http.FileServer(root)
34+
mux.Handle("/", fs)
35+
} else {
36+
mux.HandleFunc("/~/status/{code}", handler.handleWithCode)
37+
mux.HandleFunc("/~/timeout", handler.handleTimeout)
38+
mux.HandleFunc("/", handler.handleDefault)
39+
}
3240

3341
s := &http.Server{
3442
Addr: fmt.Sprintf(":%d", opts.Port),
@@ -68,7 +76,8 @@ func (s *Server) Close() error {
6876
}
6977

7078
func ListRoutes() {
71-
fmt.Println("/~/with-status/{code} Respond with the given code as status.")
72-
fmt.Println("/~/timeout Endpoint that hangs the request (for 5 min).")
73-
fmt.Println("/* Echo the request with 200 OK status code.")
79+
fmt.Println("/~/status/{code} Respond with the given code as status.")
80+
fmt.Println(" Send 'random' as the path parameter to get a random status.")
81+
fmt.Println("/~/timeout Endpoint that hangs the request (for 5 min).")
82+
fmt.Println("/* Echo the request with 200 OK status code.")
7483
}

0 commit comments

Comments
 (0)