├── .github └── workflows │ ├── ci.yml │ ├── docker.yaml │ └── release.yml ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── go.mod └── main.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@master 17 | - name: Setup Go 18 | uses: actions/setup-go@v2 19 | with: 20 | go-version: 1.x 21 | - name: Build 22 | run: go build -v . 23 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Docker image 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | push_to_registry: 10 | name: Docker 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out the repo 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up QEMU 17 | uses: docker/setup-qemu-action@v1 18 | 19 | - name: Set up Docker Buildx 20 | id: buildx 21 | uses: docker/setup-buildx-action@v1 22 | 23 | - name: Log in to GitHub Container Registry 24 | uses: docker/login-action@v2 25 | with: 26 | registry: ghcr.io 27 | username: ${{ github.actor }} 28 | password: ${{ secrets.GITHUB_TOKEN }} 29 | 30 | - name: Extract metadata (tags, labels) for Docker 31 | id: meta 32 | uses: docker/metadata-action@v3 33 | with: 34 | images: ghcr.io/${{ github.repository }} 35 | 36 | - name: Build and push Docker image 37 | uses: docker/build-push-action@v2 38 | with: 39 | context: . 40 | platforms: linux/amd64 41 | builder: ${{ steps.buildx.outputs.name }} 42 | push: ${{ github.event_name != 'pull_request' }} 43 | tags: | 44 | ghcr.io/${{ github.repository }}:latest 45 | ${{ steps.meta.outputs.tags }} 46 | labels: ${{ steps.meta.outputs.labels }} 47 | cache-from: type=gha 48 | cache-to: type=gha,mode=max # mode=maxを有効にすると、中間ステージまで含めてキャッシュできる 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@master 15 | - name: Setup Go 16 | uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.21.x 19 | - name: Cross build 20 | run: make cross 21 | - name: Create Release 22 | id: create_release 23 | uses: actions/create-release@master 24 | env: 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | with: 27 | tag_name: ${{ github.ref }} 28 | release_name: Release ${{ github.ref }} 29 | - name: Upload 30 | run: make upload 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | serve* 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1.4 2 | 3 | FROM golang:1.21-alpine AS build-dev 4 | WORKDIR /go/src/app 5 | COPY --link go.* ./ 6 | RUN apk add --no-cache upx || \ 7 | go version && \ 8 | go mod download 9 | COPY --link . . 10 | RUN CGO_ENABLED=0 go install -buildvcs=false -trimpath -ldflags '-w -s' 11 | RUN [ -e /usr/bin/upx ] && upx /go/bin/serve || echo 12 | FROM scratch 13 | COPY --link --from=build-dev /go/bin/serve /go/bin/serve 14 | COPY --from=build-dev /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 15 | CMD ["/go/bin/serve"] 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BIN := serve 2 | VERSION := $$(make -s show-version) 3 | CURRENT_REVISION := $(shell git rev-parse --short HEAD) 4 | BUILD_LDFLAGS := "-s -w -X main.revision=$(CURRENT_REVISION)" 5 | GOBIN ?= $(shell go env GOPATH)/bin 6 | export GO111MODULE=on 7 | 8 | .PHONY: all 9 | all: clean build 10 | 11 | .PHONY: build 12 | build: 13 | go build -ldflags=$(BUILD_LDFLAGS) -o $(BIN) . 14 | 15 | .PHONY: install 16 | install: 17 | go install -ldflags=$(BUILD_LDFLAGS) . 18 | 19 | .PHONY: show-version 20 | show-version: $(GOBIN)/gobump 21 | @gobump show -r . 22 | 23 | $(GOBIN)/gobump: 24 | @go install github.com/x-motemen/gobump/cmd/gobump@latest 25 | 26 | .PHONY: cross 27 | cross: $(GOBIN)/goxz 28 | @goxz -n $(BIN) -pv=v$(VERSION) -build-ldflags=$(BUILD_LDFLAGS) . 29 | 30 | $(GOBIN)/goxz: 31 | @go install github.com/Songmu/goxz/cmd/goxz@latest 32 | 33 | .PHONY: test 34 | test: build 35 | @go test -v ./... 36 | 37 | .PHONY: clean 38 | clean: 39 | rm -rf $(BIN) goxz 40 | go clean 41 | 42 | .PHONY: bump 43 | bump: $(GOBIN)/gobump 44 | ifneq ($(shell git status --porcelain),) 45 | $(error git workspace is dirty) 46 | endif 47 | ifneq ($(shell git rev-parse --abbrev-ref HEAD),master) 48 | $(error current branch is not master) 49 | endif 50 | @gobump up -w . 51 | git commit -am "bump up version to $(VERSION)" 52 | git tag "v$(VERSION)" 53 | git push origin master 54 | git push origin "refs/tags/v$(VERSION)" 55 | 56 | .PHONY: upload 57 | upload: $(GOBIN)/ghr 58 | ghr "v$(VERSION)" goxz 59 | 60 | $(GOBIN)/ghr: 61 | @go install github.com/tcnksm/ghr@latest 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # serve 2 | 3 | Serve static files as soon as you want! 4 | 5 | ## Usage 6 | 7 | ### Serve files under current directory as / 8 | 9 | ``` 10 | $ serve 11 | 2014/10/29 19:36:41 serving /home/mattn as / on :5000 12 | ``` 13 | 14 | ### Serve files under /tmp as / 15 | 16 | ``` 17 | $ serve -r /tmp 18 | 2014/10/29 19:36:41 serving /tmp as / on :5000 19 | ``` 20 | 21 | ### Serve files under /tmp as /foo 22 | 23 | ``` 24 | $ serve -r /tmp -p /foo 25 | 2014/10/29 19:36:41 serving /tmp as /foo on :5000 26 | ``` 27 | 28 | ### Serve files under /tmp as /foo on 192.168.0.3:80 29 | 30 | ``` 31 | $ serve -r /tmp -p /foo -a 192.168.0.3:80 32 | 2014/10/29 19:36:41 serving /tmp as /foo on 192.168.0.3:80 33 | ``` 34 | 35 | ## Requirements 36 | 37 | golang 38 | 39 | ## Installation 40 | 41 | ``` 42 | $ go install github.com/mattn/serve@latest 43 | ``` 44 | 45 | ## License 46 | 47 | MIT 48 | 49 | ## Author 50 | 51 | Yasuhiro Matsumoto (a.k.a mattn) 52 | 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/serve 2 | 3 | go 1.21 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "path/filepath" 11 | "runtime" 12 | ) 13 | 14 | const name = "serve" 15 | 16 | const version = "0.0.4" 17 | 18 | var revision = "HEAD" 19 | 20 | func main() { 21 | addr := flag.String("a", ":5000", "address to serve(host:port)") 22 | prefix := flag.String("p", "/", "prefix path under") 23 | root := flag.String("r", ".", "root path to serve") 24 | certFile := flag.String("cf", "", "tls cert file") 25 | keyFile := flag.String("kf", "", "tls key file") 26 | dumpPost := flag.Bool("dumpPost", false, "dump post data") 27 | showVersion := flag.Bool("v", false, "show version") 28 | flag.Parse() 29 | 30 | if *showVersion { 31 | fmt.Printf("%s %s (rev: %s/%s)\n", name, version, revision, runtime.Version()) 32 | return 33 | } 34 | 35 | var err error 36 | *root, err = filepath.Abs(*root) 37 | if err != nil { 38 | log.Fatalln(err) 39 | } 40 | log.Printf("serving %s as %s on %s", *root, *prefix, *addr) 41 | 42 | http.Handle(*prefix, http.StripPrefix(*prefix, http.FileServer(http.Dir(*root)))) 43 | 44 | mux := http.DefaultServeMux.ServeHTTP 45 | 46 | var logger http.HandlerFunc 47 | if *dumpPost { 48 | logger = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 49 | log.Print(r.RemoteAddr + " " + r.Method + " " + r.URL.String()) 50 | io.Copy(os.Stderr, r.Body) 51 | os.Stderr.Write([]byte{'\n'}) 52 | mux(w, r) 53 | }) 54 | } else { 55 | logger = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 56 | log.Print(r.RemoteAddr + " " + r.Method + " " + r.URL.String()) 57 | mux(w, r) 58 | }) 59 | } 60 | 61 | if *certFile != "" && *keyFile != "" { 62 | err = http.ListenAndServeTLS(*addr, *certFile, *keyFile, logger) 63 | } else { 64 | err = http.ListenAndServe(*addr, logger) 65 | } 66 | if err != nil { 67 | log.Fatalln(err) 68 | } 69 | } 70 | --------------------------------------------------------------------------------