Skip to content

Commit

Permalink
Add Found method to check if resource is available (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
vearutop authored Nov 4, 2022
1 parent a98b248 commit 226a5d5
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 46 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ env:
GO111MODULE: "on"
CACHE_BENCHMARK: "off" # Enables benchmark result reuse between runs, may skew latency results.
RUN_BASE_BENCHMARK: "on" # Runs benchmark for PR base in case benchmark result is missing.
GO_VERSION: 1.18.x
GO_VERSION: 1.19.x
jobs:
bench:
runs-on: ubuntu-latest
steps:
- name: Install Go stable
if: env.GO_VERSION != 'tip'
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Install Go tip
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/cloc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ jobs:
- name: Count Lines Of Code
id: loc
run: |
curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.1/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz
curl -sLO https://github.com/vearutop/sccdiff/releases/download/v1.0.3/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz
sccdiff_hash=$(git hash-object ./sccdiff)
[ "$sccdiff_hash" == "ae8a07b687bd3dba60861584efe724351aa7ff63" ] || (echo "::error::unexpected hash for sccdiff, possible tampering: $sccdiff_hash" && exit 1)
OUTPUT=$(cd pr && ../sccdiff -basedir ../base)
echo "${OUTPUT}"
OUTPUT="${OUTPUT//$'\n'/%0A}"
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ jobs:
name: golangci-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v2
- uses: actions/setup-go@v3
with:
go-version: 1.18.x
go-version: 1.19.x
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v3.1.0
uses: golangci/golangci-lint-action@v3.2.0
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.45.2
version: v1.50.0

# Optional: working directory, useful for monorepos
# working-directory: somedir
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/gorelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ concurrency:
cancel-in-progress: true

env:
GO_VERSION: 1.18.x
GO_VERSION: 1.19.x
jobs:
gorelease:
runs-on: ubuntu-latest
steps:
- name: Install Go stable
if: env.GO_VERSION != 'tip'
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Install Go tip
Expand All @@ -35,12 +35,12 @@ jobs:
with:
path: |
~/go/bin/gorelease
key: ${{ runner.os }}-gorelease
key: ${{ runner.os }}-gorelease-generic
- name: Gorelease
id: gorelease
run: |
test -e ~/go/bin/gorelease || go install golang.org/x/exp/cmd/gorelease@latest
OUTPUT=$(gorelease || exit 0)
OUTPUT=$(gorelease 2>&1 || exit 0)
echo "${OUTPUT}"
OUTPUT="${OUTPUT//$'\n'/%0A}"
echo "::set-output name=report::$OUTPUT"
Expand Down
73 changes: 50 additions & 23 deletions .github/workflows/test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ env:
GO111MODULE: "on"
RUN_BASE_COVERAGE: "on" # Runs test for PR base in case base test coverage is missing.
COV_GO_VERSION: 1.18.x # Version of Go to collect coverage
TARGET_DELTA_COV: 90 # Target coverage of changed lines, in percents
jobs:
test:
strategy:
matrix:
go-version: [ 1.16.x, 1.17.x, 1.18.x, tip ]
go-version: [ 1.16.x, 1.17.x, 1.18.x, 1.19.x ]
runs-on: ubuntu-latest
steps:
- name: Install Go stable
if: matrix.go-version != 'tip'
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: ${{ matrix.go-version }}

- name: Install Go tip
if: matrix.go-version == 'tip'
run: |
Expand All @@ -37,8 +39,10 @@ jobs:
tar -C ~/sdk/gotip -xzf gotip.tar.gz
~/sdk/gotip/bin/go version
echo "PATH=$HOME/go/bin:$HOME/sdk/gotip/bin/:$PATH" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v2

- name: Go cache
uses: actions/cache@v2
with:
Expand All @@ -51,58 +55,81 @@ jobs:
key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-cache
- name: Restore base test coverage
id: base-coverage
if: matrix.go-version == env.COV_GO_VERSION
if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != ''
uses: actions/cache@v2
with:
path: |
unit-base.txt
# Use base sha for PR or new commit hash for master/main push in test result key.
key: ${{ runner.os }}-unit-test-coverage-${{ (github.event.pull_request.base.sha != github.event.after) && github.event.pull_request.base.sha || github.event.after }}
- name: Checkout base code
if: matrix.go-version == env.COV_GO_VERSION && env.RUN_BASE_COVERAGE == 'on' && steps.base-coverage.outputs.cache-hit != 'true' && github.event.pull_request.base.sha != ''
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.base.sha }}
path: __base

- name: Run test for base code
if: matrix.go-version == env.COV_GO_VERSION && env.RUN_BASE_COVERAGE == 'on' && steps.base-coverage.outputs.cache-hit != 'true' && github.event.pull_request.base.sha != ''
run: |
cd __base
make | grep test-unit && (make test-unit && go tool cover -func=./unit.coverprofile | sed -e 's/.go:[0-9]*:\t/.go\t/g' | sed -e 's/\t\t*/\t/g' > ../unit-base.txt) || echo "No test-unit in base"
git fetch origin master ${{ github.event.pull_request.base.sha }}
HEAD=$(git rev-parse HEAD)
git reset --hard ${{ github.event.pull_request.base.sha }}
(make test-unit && go tool cover -func=./unit.coverprofile > unit-base.txt) || echo "No test-unit in base"
git reset --hard $HEAD
- name: Test
id: test
run: |
make test-unit
go tool cover -func=./unit.coverprofile | sed -e 's/.go:[0-9]*:\t/.go\t/g' | sed -e 's/\t\t*/\t/g' > unit.txt
OUTPUT=$(test -e unit-base.txt && (diff unit-base.txt unit.txt || exit 0) || cat unit.txt)
echo "${OUTPUT}"
OUTPUT="${OUTPUT//$'\n'/%0A}"
go tool cover -func=./unit.coverprofile > unit.txt
TOTAL=$(grep 'total:' unit.txt)
echo "${TOTAL}"
echo "::set-output name=diff::$OUTPUT"
echo "::set-output name=total::$TOTAL"
- name: Store base coverage
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
run: cp unit.txt unit-base.txt
- name: Annotate missing test coverage
id: annotate
if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != ''
run: |
curl -sLO https://github.com/vearutop/gocovdiff/releases/download/v1.3.6/linux_amd64.tar.gz && tar xf linux_amd64.tar.gz
gocovdiff_hash=$(git hash-object ./gocovdiff)
[ "$gocovdiff_hash" == "8e507e0d671d4d6dfb3612309b72b163492f28eb" ] || (echo "::error::unexpected hash for gocovdiff, possible tampering: $gocovdiff_hash" && exit 1)
git fetch origin master ${{ github.event.pull_request.base.sha }}
REP=$(./gocovdiff -cov unit.coverprofile -gha-annotations gha-unit.txt -delta-cov-file delta-cov-unit.txt -target-delta-cov ${TARGET_DELTA_COV})
echo "${REP}"
REP="${REP//$'\n'/%0A}"
cat gha-unit.txt
DIFF=$(test -e unit-base.txt && ./gocovdiff -func-cov unit.txt -func-base-cov unit-base.txt || echo "Missing base coverage file")
DIFF="${DIFF//$'\n'/%0A}"
TOTAL=$(cat delta-cov-unit.txt)
echo "::set-output name=rep::$REP"
echo "::set-output name=diff::$DIFF"
echo "::set-output name=total::$TOTAL"
- name: Comment Test Coverage
continue-on-error: true
if: matrix.go-version == env.COV_GO_VERSION
if: matrix.go-version == env.COV_GO_VERSION && github.event.pull_request.base.sha != ''
uses: marocchino/sticky-pull-request-comment@v2
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
header: unit-test
message: |
### Unit Test Coverage
${{ steps.test.outputs.total }}
${{ steps.annotate.outputs.total }}
<details><summary>Coverage of changed lines</summary>
${{ steps.annotate.outputs.rep }}
</details>
<details><summary>Coverage diff with base branch</summary>
```diff
${{ steps.test.outputs.diff }}
```
${{ steps.annotate.outputs.diff }}
</details>
- name: Store base coverage
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' }}
run: cp unit.txt unit-base.txt

- name: Upload code coverage
if: matrix.go-version == env.COV_GO_VERSION
uses: codecov/codecov-action@v1
Expand Down
17 changes: 17 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,33 @@ linters:
- tagliatelle
- errname
- ireturn
- exhaustruct
- nonamedreturns
- nosnakecase
- structcheck
- varcheck
- deadcode
- testableexamples
- dupword

issues:
exclude-use-default: false
exclude-rules:
- linters:
- gosec
- gomnd
- goconst
- goerr113
- noctx
- funlen
- dupl
- structcheck
- unused
- unparam
- nosnakecase
path: "_test.go"
- linters:
- errcheck # Error checking omitted for brevity.
- gosec
path: "example_"

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#GOLANGCI_LINT_VERSION := "v1.45.0" # Optional configuration to pinpoint golangci-lint version.
#GOLANGCI_LINT_VERSION := "v1.50.0" # Optional configuration to pinpoint golangci-lint version.

# The head of Makefile determines location of dev-go to include standard targets.
GO ?= go
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,30 @@ func main() {
}
}
```

### Custom handling of Not Found

If you need special treatment for resources that are not available in static server, you can use `Found`
to check them before serving.

```go
fileServer := statigz.FileServer(st)
customHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Serve existing static resource.
if fileServer.Found(r) {
fileServer.ServeHTTP(w, r)

return
}

// Do something custom for non-existing resource, for example serve index page.
// (This is an example, serving index instead of 404 might not be the best idea in real life 😅).
r.URL.Path = "/"
fileServer.ServeHTTP(w, r)
})

// Plug static assets handler to your server or router.
if err := http.ListenAndServe("localhost:80", customHandler); err != nil {
log.Fatal(err)
}
```
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.17

require (
github.com/andybalholm/brotli v1.0.4
github.com/bool64/dev v0.2.9
github.com/bool64/dev v0.2.22
github.com/stretchr/testify v1.4.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/bool64/dev v0.2.9 h1:efyGf5pgx4CYWQpCzPEX8a1PgewaCGaEexXa+IYHT/8=
github.com/bool64/dev v0.2.9/go.mod h1:/csLrm+4oDSsKJRIVS0mrywAonLnYKFG8RvGT7Jh9b8=
github.com/bool64/dev v0.2.22 h1:YJFKBRKplkt+0Emq/5Xk1Z5QRmMNzc1UOJkR3rxJksA=
github.com/bool64/dev v0.2.22/go.mod h1:iJbh1y/HkunEPhgebWRNcs8wfGq7sjvJ6W5iabL8ACg=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
49 changes: 46 additions & 3 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ var SkipCompressionExt = []string{".gz", ".br", ".gif", ".jpg", ".png", ".webp"}
//
// Typically, file system would be an embed.FS.
//
// //go:embed *.png *.br
// var FS embed.FS
// //go:embed *.png *.br
// var FS embed.FS
//
// Brotli support is optionally available with brotli.AddEncoding.
func FileServer(fs fs.ReadDirFS, options ...func(server *Server)) *Server {
Expand Down Expand Up @@ -367,6 +367,49 @@ func (s *Server) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
http.NotFound(rw, req)
}

// Found returns true if http.Request would be fulfilled by Server.
//
// This can be useful for custom handling of requests to non-existent resources.
func (s *Server) Found(req *http.Request) bool {
fn := strings.TrimPrefix(req.URL.Path, "/")
ae := req.Header.Get("Accept-Encoding")

if s.info[fn].isDir {
return true
}

if fn == "" || strings.HasSuffix(fn, "/") {
fn += "index.html"
}

if ae != "" {
minInfo, _ := s.minEnc(strings.ToLower(ae), fn)

if minInfo.hash != "" {
// Copy compressed data into response.
return true
}
}

// Copy uncompressed data into response.
_, uncompressedFound := s.info[fn]
if uncompressedFound {
return true
}

// Decompress compressed data into response.
for _, enc := range s.Encodings {
info, found := s.info[fn+enc.FileExt]
if !found || enc.Decoder == nil || info.isDir {
continue
}

return true
}

return false
}

// Encoding describes content encoding.
type Encoding struct {
// FileExt is an extension of file with compressed content, for example ".gz".
Expand Down Expand Up @@ -435,7 +478,7 @@ func EncodeOnInit(server *Server) {
// localRedirect gives a Moved Permanently response.
// It does not convert relative paths to absolute paths like Redirect does.
//
// Copied go1.17/src/net/http/fs.go:685.
// Copied from go1.17/src/net/http/fs.go:685.
func localRedirect(w http.ResponseWriter, r *http.Request, newPath string) {
if q := r.URL.RawQuery; q != "" {
newPath += "?" + q
Expand Down
Loading

0 comments on commit 226a5d5

Please sign in to comment.