Compare commits

..

No commits in common. "main" and "v2.0.0" have entirely different histories.
main ... v2.0.0

14 changed files with 72 additions and 370 deletions

View file

@ -1,81 +0,0 @@
name: Build
on:
push:
branches:
- main
- beta
- develop
pull_request:
jobs:
checks:
name: Checks
runs-on: docker
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
check-latest: true
- name: Run go fmt and go vet
run: |
go fmt $(go list ./...)
go vet $(go list ./...)
code-coverage:
name: Code Coverage
runs-on: docker
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
check-latest: true
- name: Run tests and generate coverage report
run: |
go test -covermode=count -coverprofile coverage.cov $(go list ./...)
go tool cover -func=coverage.cov
go tool cover -html=coverage.cov -o coverage.html
- name: Upload coverage artifacts
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
with:
name: coverage-reports
path: |
coverage.cov
coverage.html
build:
name: Build
runs-on: docker
strategy:
matrix:
go:
- GOOS: darwin
GOARCH: amd64
- GOOS: darwin
GOARCH: arm64
- GOOS: linux
GOARCH: amd64
- GOOS: linux
GOARCH: arm64
- GOOS: windows
GOARCH: amd64
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'
check-latest: true
- name: Set environment variables
run: |
echo "GOOS=${{ matrix.go.GOOS }}" >> $GITHUB_ENV
echo "GOARCH=${{ matrix.go.GOARCH }}" >> $GITHUB_ENV
- name: Build
run: go build .

View file

@ -1,20 +0,0 @@
name: Release
on:
push:
branches:
- main
- beta
jobs:
release:
name: Semantic Release
runs-on: docker
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Semantic Release
shell: bash
run: |
npm install -g semantic-release@23 conventional-changelog-conventionalcommits@7
semantic-release

1
.gitignore vendored
View file

@ -1,3 +1,2 @@
.DS_Store
.vscode
.idea

38
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,38 @@
image: golang:latest
stages:
- test
validate lint:
stage: test
script:
- go get -u golang.org/x/lint/golint
- golint -set_exit_status ./...
execute tests:
stage: test
script:
- go test ./...
race detection:
stage: test
script:
- go test -race ./...
code coverage:
stage: test
script:
- go test -covermode=count -coverprofile coverage.cov ./...
- go tool cover -func=coverage.cov
- mkdir $CI_PROJECT_DIR/report
- go tool cover -html=coverage.cov -o $CI_PROJECT_DIR/report/coverage.html
coverage: '/\(statements\)\W+\d+\.\d+%/'
artifacts:
paths:
- report/
codecov.io:
stage: test
script:
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- bash <(curl -s https://codecov.io/bash)

View file

@ -1,72 +0,0 @@
{
"branches": [
"main",
{
"name": "beta",
"prerelease": true
}
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{
"type": "chore",
"release": "patch"
},
{
"type": "build",
"release": "patch"
},
{
"type": "ci",
"release": "patch"
}
]
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{
"type": "feat",
"section": "Features"
},
{
"type": "feature",
"section": "Features"
},
{
"type": "fix",
"section": "Bug Fixes"
},
{
"type": "perf",
"section": "Performance Improvements"
},
{
"type": "revert",
"section": "Reverts"
},
{
"type": "chore",
"section": "Miscellaneous Chores"
}
]
}
}
],
[
"@semantic-release/github",
{
"successCommentCondition": false,
"failTitle": false
}
]
]
}

View file

@ -1,19 +1,17 @@
# httprouter
[![release](https://git.martin-riedl.de/golang/httprouter/badges/release.svg)](https://git.martin-riedl.de/golang/httprouter/tags)
[![pipeline status](https://git.martin-riedl.de/golang/httprouter/badges/workflows/build.yml/badge.svg)](https://git.martin-riedl.de/golang/httprouter/actions)
# goHTTPRouter
[![GoDoc](https://godoc.org/gitlab.com/martinr92/gohttprouter?status.svg)](https://godoc.org/gitlab.com/martinr92/gohttprouter)
[![pipeline status](https://gitlab.com/martinr92/gohttprouter/badges/master/pipeline.svg)](https://gitlab.com/martinr92/gohttprouter/commits/master)
[![coverage report](https://gitlab.com/martinr92/gohttprouter/badges/master/coverage.svg)](https://gitlab.com/martinr92/gohttprouter/commits/master)
[![codecov](https://codecov.io/gl/martinr92/gohttprouter/branch/master/graph/badge.svg)](https://codecov.io/gl/martinr92/gohttprouter)
[![Go Report Card](https://goreportcard.com/badge/gitlab.com/martinr92/gohttprouter)](https://goreportcard.com/report/gitlab.com/martinr92/gohttprouter)
httprouter is a framework used for HTTP request routing.
goHTTPRouter is a framework used for HTTP request routing.
# Examples
## Simple Routing
Just replace the standard router of golang with this one:
```golang
import "git.martin-riedl.de/golang/httprouter/v4"
import httprouter "gitlab.com/martinr92/gohttprouter/v2"
```
```golang
router := httprouter.New()
@ -24,10 +22,9 @@ err := http.ListenAndServe("localhost:8080", router)
```
## Routing with placeholder
A path can also contain placeholder (like :id). If then a request is sent to the url "/user/123" the method gets executed and the URL part (in this case "123") is passed as parameter into your handler function.
```golang
import "git.martin-riedl.de/golang/httprouter/v4"
import httprouter "gitlab.com/martinr92/gohttprouter/v2"
```
```golang
router := httprouter.New()
@ -41,12 +38,11 @@ err := http.ListenAndServe("localhost:8080", router)
```
## Serve Static Content
Static files (like JavaScript or CSS) can be served automatically (including caching header and MIME type). It uses the `embed.FS` (since go 1.16) to serve static content.
```golang
import "git.martin-riedl.de/golang/httprouter/v4"
import httprouter "gitlab.com/martinr92/gohttprouter/v2"
//go:embed files/static/*
//go:embed files/statc/*
var staticFiles embed.FS
```
```golang
@ -55,12 +51,9 @@ router := httprouter.New()
router.Handle(http.MethodGet, "/static/*", staticFS)
```
For development purpose you can enable the local file serving additionally.
The framework checks first, if the file exists locally and serves it directly.
If not, the file is served from the `embed.FS`.
This helps you during local development so you can modify a CSS file without recompiling everything.
For development purpose you can enable the local file serving additionally. The framework checks first, if the file exists locally and serves it directly. If not, the file is served from the `embed.FS`. This helps you during local development so you can modify a CSS file without recompiling everything.
```golang
import "git.martin-riedl.de/golang/httprouter/v4"
import httprouter "gitlab.com/martinr92/gohttprouter/v2"
```
```golang
staticFS := httprouter.NewFS(&staticFiles)
@ -69,9 +62,8 @@ staticFS.LocalFolderPrefix = "some/folder" // optional
```
# License
```
Copyright 2018-2025 Martin Riedl
Copyright 2018-2021 Martin Riedl
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

54
fs.go
View file

@ -1,4 +1,4 @@
// Copyright 2021-2025 Martin Riedl
// Copyright 2021 Martin Riedl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httprouter
package gohttprouter
import (
"embed"
@ -32,15 +32,14 @@ type FS struct {
FolderPrefix string
UseLocalFolder bool
LocalFolderPrefix string
HeaderHandler HeaderHandler
IndexFileNames []string
HeaderHeandler HeaderHandler
}
// NewFS creates a new instance of the http file system used for serving static files.
func NewFS(staticFiles *embed.FS) *FS {
return &FS{
StaticFiles: staticFiles,
HeaderHandler: DefaultHeaderHandler,
HeaderHeandler: DefaultHeaderHandler,
}
}
@ -57,12 +56,6 @@ func (fs *FS) ServeHTTP(w http.ResponseWriter, r *http.Request, info RoutingInfo
http.NotFound(w, r)
return
}
// check, if the file is a folder
file, ok := fs.checkFolder(file, url)
if !ok {
http.NotFound(w, r)
return
}
defer file.Close()
// serve file content
@ -78,9 +71,6 @@ func (fs *FS) findFile(url string) (fs.File, error) {
if fs.UseLocalFolder {
localFilePath := path.Join(fs.LocalFolderPrefix, filePath)
localFilePath = strings.TrimPrefix(localFilePath, "/")
if localFilePath == "" {
localFilePath = "."
}
file, err := os.Open(localFilePath)
if err == nil {
@ -89,43 +79,13 @@ func (fs *FS) findFile(url string) (fs.File, error) {
}
// use static file system
if filePath == "" {
filePath = "."
}
file, err := fs.StaticFiles.Open(filePath)
return file, err
}
func (fs *FS) checkFolder(file fs.File, url string) (fs.File, bool) {
fileInfo, err := file.Stat()
if err != nil {
_ = file.Close()
return file, false
}
// check for folder / root location
if fileInfo.IsDir() {
// close folder handler (we don't need it anymore)
_ = file.Close()
// check for index files
for _, indexFileName := range fs.IndexFileNames {
newFullPath := path.Join(url, indexFileName)
if newFile, err := fs.findFile(newFullPath); err == nil {
return newFile, true
}
}
return file, false
}
return file, true
return fs.StaticFiles.Open(filePath)
}
func (fs *FS) serve(w http.ResponseWriter, r *http.Request, info RoutingInfo, file fs.File) {
// execute header handler
if fs.HeaderHandler != nil {
fs.HeaderHandler(w, r, info, file)
if fs.HeaderHeandler != nil {
fs.HeaderHeandler(w, r, info, file)
}
w.WriteHeader(http.StatusOK)

View file

@ -1,18 +1,4 @@
// Copyright 2025 Martin Riedl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httprouter
package gohttprouter
import (
"embed"
@ -128,69 +114,6 @@ func TestFSLicense(t *testing.T) {
}
}
func TestFSFolder(t *testing.T) {
// create new FS
fs := NewFS(&staticFiles)
// serve the request
tw := &testWriter{}
tr := &http.Request{
Method: http.MethodHead,
URL: &url.URL{
Path: "/",
},
}
fs.ServeHTTP(tw, tr, RoutingInfo{})
// check data
if tw.statusCode != http.StatusNotFound {
t.Error("received invalid http status code", tw.statusCode)
}
}
func TestFSFolderLocal(t *testing.T) {
// create new FS
fs := NewFS(&staticFiles)
fs.UseLocalFolder = true
fs.LocalFolderPrefix = "/"
// serve the request
tw := &testWriter{}
tr := &http.Request{
Method: http.MethodHead,
URL: &url.URL{
Path: "/",
},
}
fs.ServeHTTP(tw, tr, RoutingInfo{})
// check data
if tw.statusCode != http.StatusNotFound {
t.Error("received invalid http status code", tw.statusCode)
}
}
func TestFSFolderIndex(t *testing.T) {
// create new FS
fs := NewFS(&staticFiles)
fs.IndexFileNames = []string{"README.md"}
// serve the request
tw := &testWriter{}
tr := &http.Request{
Method: http.MethodHead,
URL: &url.URL{
Path: "/",
},
}
fs.ServeHTTP(tw, tr, RoutingInfo{})
// check data
if tw.statusCode != http.StatusOK {
t.Error("received invalid http status code", tw.statusCode)
}
}
func TestFSNotFound(t *testing.T) {
// create new FS
fs := NewFS(&staticFiles)

4
go.mod
View file

@ -1,3 +1,3 @@
module git.martin-riedl.de/golang/httprouter/v4
module gitlab.com/martinr92/gohttprouter/v2
go 1.19
go 1.16

View file

@ -1,9 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>ci/renovate//configs/base"
],
"baseBranches": [
"develop"
]
}

View file

@ -1,4 +1,4 @@
// Copyright 2018-2025 Martin Riedl
// Copyright 2018-2021 Martin Riedl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httprouter
package gohttprouter
import (
"strings"
@ -82,7 +82,7 @@ func (r *route) parsePath(path string, create bool) (*route, bool, map[string]st
if !ok && !create {
// no route found
return nil, false, nil
} else if !ok {
} else if !ok && create {
subRoute = newRoute()
subRoute.parseName(subName)
r.routes[subRoute.name] = subRoute
@ -91,7 +91,7 @@ func (r *route) parsePath(path string, create bool) (*route, bool, map[string]st
// parse sub-route
matchingRoute, created, subParameters := subRoute.parsePath(parts[1], create)
return matchingRoute, subRouteCreated || created, mergeParameterMaps(parameters, subParameters)
return matchingRoute, (subRouteCreated || created), mergeParameterMaps(parameters, subParameters)
}
// last element in path

View file

@ -1,18 +1,4 @@
// Copyright 2025 Martin Riedl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httprouter
package gohttprouter
import (
"testing"

View file

@ -1,4 +1,4 @@
// Copyright 2018-2025 Martin Riedl
// Copyright 2018-2021 Martin Riedl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package httprouter
package gohttprouter
import (
"fmt"
@ -85,7 +85,7 @@ func (router *Router) findRoute(method string, path string, create bool) (route
methodRoute, ok := router.registry[methodUpper]
if !ok && !create {
return nil, false, nil
} else if !ok {
} else if !ok && create {
methodRoute = newRoute()
router.registry[methodUpper] = methodRoute
created = true

View file

@ -1,18 +1,4 @@
// Copyright 2025 Martin Riedl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httprouter
package gohttprouter
import (
"net"