Repository: bnkamalesh/webgo
Branch: master
Commit: 8ee6c6e23f3b
Files: 52
Total size: 150.0 KB
Directory structure:
gitextract_26pnjm80/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── go.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── _config.yml
├── cmd/
│ ├── README.md
│ ├── certs/
│ │ ├── CA.key
│ │ ├── CA.pem
│ │ ├── CA.srl
│ │ ├── localhost.crt
│ │ ├── localhost.csr
│ │ ├── localhost.decrypted.key
│ │ ├── localhost.ext
│ │ └── localhost.key
│ ├── handlers.go
│ ├── main.go
│ └── static/
│ ├── css/
│ │ ├── main.css
│ │ └── normalize.css
│ ├── index.html
│ └── js/
│ ├── main.js
│ └── sse.js
├── config.go
├── config_test.go
├── errors.go
├── errors_test.go
├── extensions/
│ └── sse/
│ ├── README.md
│ ├── client.go
│ ├── message.go
│ └── sse.go
├── go.mod
├── go.sum
├── middleware/
│ ├── accesslog/
│ │ ├── accesslog.go
│ │ └── accesslog_test.go
│ └── cors/
│ ├── cors.go
│ └── cors_test.go
├── responses.go
├── responses_test.go
├── route.go
├── route_test.go
├── router.go
├── router_test.go
├── tests/
│ ├── config.json
│ └── ssl/
│ ├── server.crt
│ ├── server.csr
│ └── server.key
├── webgo.go
└── webgo_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: [bnkamalesh] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/go.yml
================================================
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: ["master"]
pull_request:
branches: ["master"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.23"
- name: Build
run: go build -v ./...
- name: Tests
run: |
go install github.com/mattn/goveralls@latest
go test -covermode atomic -coverprofile=covprofile $(go list ./... | grep -v /cmd | grep -v /extensions/)
- name: Send coverage
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: covprofile
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:
version: v1.60
================================================
FILE: .gitignore
================================================
# Created by https://www.gitignore.io/api/go,osx,linux,windows
### Go ###
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Golang project vendor packages which should be ignored
vendor/
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### OSX ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/go,osx,linux,windows
.vscode
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at bn_kamalesh@yahoo.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: CONTRIBUTING.md
================================================
Contributions are welcome from everyone. Please adhere to the [code of conduct](https://github.com/naughtygopher/webgo/blob/master/CODE_OF_CONDUCT.md) of the project, and be respectful to all.
Please follow the guidelines provided for contribution
1. Updates to the project are only accepted via Pull Requests (PR)
2. Pull requests will be reviewed & tested
3. Every PR should be accompanied by its test wherever applicable
4. While creating an issue
1. Mention the steps to reproduce the issue
2. Mention the environment in which it was run
3. Include your 1st level of troubleshooting results
5. Provide meaningful commit messages
### Versioning & PR messages
WebGo tries to use [semantic versioning](https://semver.org/) and starting recently, have decided to adhere to the following syntax in PR description. List down the changes as bulleted list, as follows:
```markdown
[major] any backward incompatible or breaking change
[minor] any new feature
[patch] enhancements of existing features, refactor, bug fix etc.
[-] for changes which does not require a version number update
```
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 Naughty Gopher
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
[](https://github.com/naughtygopher/webgo/actions)
[](https://pkg.go.dev/github.com/naughtygopher/webgo)
[](https://goreportcard.com/report/github.com/naughtygopher/webgo)
[](https://coveralls.io/github/naughtygopher/webgo?branch=master)
[](https://github.com/avelino/awesome-go#web-frameworks)
[](https://github.com/creativecreature/sturdyc/blob/master/LICENSE)
# WebGo v7.0.4
WebGo is a minimalistic router for [Go](https://golang.org) to build web applications (server side) with no 3rd party dependencies. WebGo will always be Go standard library compliant; with the HTTP handlers having the same signature as [http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc).
### Contents
1. [Router](https://github.com/naughtygopher/webgo#router)
2. [Handler chaining](https://github.com/naughtygopher/webgo#handler-chaining)
3. [Middleware](https://github.com/naughtygopher/webgo#middleware)
4. [Error handling](https://github.com/naughtygopher/webgo#error-handling)
5. [Helper functions](https://github.com/naughtygopher/webgo#helper-functions)
6. [HTTPS ready](https://github.com/naughtygopher/webgo#https-ready)
7. [Graceful shutdown](https://github.com/naughtygopher/webgo#graceful-shutdown)
8. [Logging](https://github.com/naughtygopher/webgo#logging)
9. [Server-Sent Events](https://github.com/naughtygopher/webgo#server-sent-events)
10. [Usage](https://github.com/naughtygopher/webgo#usage)
## Router
Webgo has a simplistic, linear path matching router and supports defining [URI](https://developer.mozilla.org/en-US/docs/Glossary/URI)s with the following patterns
1. `/api/users` - URI with no dynamic values
2. `/api/users/:userID`
- URI with a named parameter, `userID`
- If TrailingSlash is set to true, it will accept the URI ending with a '/', refer to [sample](https://github.com/naughtygopher/webgo#sample)
3. `/api/users/:misc*`
- Named URI parameter `misc`, with a wildcard suffix '\*'
- This matches everything after `/api/users`. e.g. `/api/users/a/b/c/d`
When there are multiple handlers matching the same URI, only the first occurring handler will handle the request.
Refer to the [sample](https://github.com/naughtygopher/webgo#sample) to see how routes are configured. You can access named parameters of the URI using the `Context` function.
Note: webgo Context is **not** available inside the special handlers (not found & method not implemented)
```golang
func helloWorld(w http.ResponseWriter, r *http.Request) {
// WebGo context
wctx := webgo.Context(r)
// URI paramaters, map[string]string
params := wctx.Params()
// route, the webgo.Route which is executing this request
route := wctx.Route
webgo.R200(
w,
fmt.Sprintf(
"Route name: '%s', params: '%s'",
route.Name,
params,
),
)
}
```
## Handler chaining
Handler chaining lets you execute multiple handlers for a given route. Execution of a chain can be configured to run even after a handler has written a response to the HTTP request, if you set `FallThroughPostResponse` to `true` (refer [sample](https://github.com/naughtygopher/webgo/blob/master/cmd/main.go#L70)).
## Middleware
WebGo [middlware](https://godoc.org/github.com/naughtygopher/webgo#Middleware) lets you wrap all the routes with a middleware unlike handler chaining. The router exposes a method [Use](https://godoc.org/github.com/naughtygopher/webgo#Router.Use) && [UseOnSpecialHandlers](https://godoc.org/github.com/naughtygopher/webgo#Router.UseOnSpecialHandlers) to add a Middleware to the router.
NotFound && NotImplemented are considered `Special` handlers. `webgo.Context(r)` within special handlers will return `nil`.
Any number of middleware can be added to the router, the order of execution of middleware would be [LIFO]() (Last In First Out). i.e. in case of the following code
```golang
func main() {
router.Use(accesslog.AccessLog, cors.CORS(nil))
router.Use()
}
```
**_CorsWrap_** would be executed first, followed by **_AccessLog_**.
## Error handling
Webgo context has 2 methods to [set](https://github.com/naughtygopher/webgo/blob/master/webgo.go#L60) & [get](https://github.com/naughtygopher/webgo/blob/master/webgo.go#L66) erro within a request context. It enables Webgo to implement a single middleware where you can handle error returned within an HTTP handler. [set error](https://github.com/naughtygopher/webgo/blob/master/cmd/main.go#L45), [get error](https://github.com/naughtygopher/webgo/blob/master/cmd/main.go#L51).
## Helper functions
WebGo provides a few helper functions. When using `Send` or `SendResponse` (other Rxxx responder functions), the response is wrapped in WebGo's [response struct](https://github.com/naughtygopher/webgo/blob/master/responses.go#L17) and is serialized as JSON.
```json
{
"data": "",
"status": ""
}
```
When using `SendError`, the response is wrapped in WebGo's [error response struct](https://github.com/naughtygopher/webgo/blob/master/responses.go#L23) and is serialzied as JSON.
```json
{
"errors": "",
"status": ""
}
```
## HTTPS ready
HTTPS server can be started easily, by providing the key & cert file. You can also have both HTTP & HTTPS servers running side by side.
Start HTTPS server
```golang
cfg := &webgo.Config{
Port: "80",
HTTPSPort: "443",
CertFile: "/path/to/certfile",
KeyFile: "/path/to/keyfile",
}
router := webgo.NewRouter(cfg, routes()...)
router.StartHTTPS()
```
Starting both HTTP & HTTPS server
```golang
cfg := &webgo.Config{
Port: "80",
HTTPSPort: "443",
CertFile: "/path/to/certfile",
KeyFile: "/path/to/keyfile",
}
router := webgo.NewRouter(cfg, routes()...)
go router.StartHTTPS()
router.Start()
```
## Graceful shutdown
Graceful shutdown lets you shutdown the server without affecting any live connections/clients connected to the server. Any new connection request after initiating a shutdown would be ignored.
Sample code to show how to use shutdown
```golang
func main() {
osSig := make(chan os.Signal, 5)
cfg := &webgo.Config{
Host: "",
Port: "8080",
ReadTimeout: 15 * time.Second,
WriteTimeout: 60 * time.Second,
ShutdownTimeout: 15 * time.Second,
}
router := webgo.NewRouter(cfg, routes()...)
go func() {
<-osSig
// Initiate HTTP server shutdown
err := router.Shutdown()
if err != nil {
fmt.Println(err)
os.Exit(1)
} else {
fmt.Println("shutdown complete")
os.Exit(0)
}
// If you have HTTPS server running, you can use the following code
// err := router.ShutdownHTTPS()
// if err != nil {
// fmt.Println(err)
// os.Exit(1)
// } else {
// fmt.Println("shutdown complete")
// os.Exit(0)
// }
}()
go func(){
time.Sleep(time.Second*15)
signal.Notify(osSig, os.Interrupt, syscall.SIGTERM)
}()
router.Start()
}
```
## Logging
WebGo exposes a singleton & global scoped logger variable [LOGHANDLER](https://godoc.org/github.com/naughtygopher/webgo#Logger) with which you can plug in your custom logger by implementing the [Logger](https://godoc.org/github.com/naughtygopher/webgo#Logger) interface.
### Configuring the default Logger
The default logger uses Go standard library's `log.Logger` with `os.Stdout` (for debug and info logs) & `os.Stderr` (for warning, error, fatal) as default io.Writers. You can set the io.Writer as well as disable specific types of logs using the `GlobalLoggerConfig(stdout, stderr, cfgs...)` function.
## Server-Sent Events
[MDN has a very good documentation of what SSE (Server-Sent Events)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) are. The sample app provided shows how to use the SSE extension of webgo.
## Usage
A fully functional sample is provided [here](https://github.com/naughtygopher/webgo/blob/master/cmd/main.go).
### Benchmark
1. [the-benchmarker](https://github.com/the-benchmarker/web-frameworks)
2. [go-web-framework-benchmark](https://github.com/smallnest/go-web-framework-benchmark)
### Contributing
Refer [here](https://github.com/naughtygopher/webgo/blob/master/CONTRIBUTING.md) to find out details about making a contribution
### Credits
Thanks to all the [contributors](https://github.com/naughtygopher/webgo/graphs/contributors)
## The gopher
The gopher used here was created using [Gopherize.me](https://gopherize.me/). WebGo stays out of developers' way, so sitback and enjoy a cup of coffee.
================================================
FILE: _config.yml
================================================
theme: jekyll-theme-cayman
================================================
FILE: cmd/README.md
================================================
# Webgo Sample
### Server Sent Events

This picture shows the sample SSE implementation provided with this application. In the sample app, the server is
sending timestamp every second, to all the clients.
**Important**: _[SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)
is a live connection between server & client. So a short WriteTimeout duration in webgo.Config will
keep dropping the connection. If you have any middleware which is setting deadlines or timeouts on the
request.Context, will also effect these connections._
## How to run
If you have Go installed on your computer, open the terminal and:
```bash
$ cd $GOPATH/src
$ git clone https://github.com/naughtygopher/webgo.git
$ cd webgo/cmd
$ go run *.go
Info 2023/02/05 08:51:26 HTTP server, listening on :8080
Info 2023/02/05 08:51:26 HTTPS server, listening on :9595
```
Or if you have [Docker](https://www.docker.com/), open the terminal and:
```bash
$ git clone https://github.com/naughtygopher/webgo.git
$ cd webgo
$ docker run \
-p 8080:8080 \
-p 9595:9595 \
-v ${PWD}:/go/src/github.com/naughtygopher/webgo/ \
-w /go/src/github.com/naughtygopher/webgo/cmd \
--rm -ti golang:latest go run *.go
Info 2023/02/05 08:51:26 HTTP server, listening on :8080
Info 2023/02/05 08:51:26 HTTPS server, listening on :9595
```
You can try the following API calls with the sample app. It also uses all the features provided by webgo
1. `http://localhost:8080/`
- Loads an HTML page
2. `http://localhost:8080/matchall/`
- Route with wildcard parameter configured
- All URIs which begin with `/matchall` will be matched because it has a wildcard variable
- e.g.
- http://localhost:8080/matchall/hello
- http://localhost:8080/matchall/hello/world
- http://localhost:8080/matchall/hello/world/user
3. `http://localhost:8080/api/`
- Route with a named 'param' configured
- It will match all requests which match `/api/`
- e.g.
- http://localhost:8080/api/hello
- http://localhost:8080/api/world
4. `http://localhost:8080/error-setter`
- Route which sets an error and sets response status 500
5. `http://localhost:8080/v7.0.0/api/`
- Route with a named 'param' configured
- It will match all requests which match `/v7.0.0/api/`
- e.g.
- http://localhost:8080/v7.0.0/api/hello
- http://localhost:8080/v7.0.0/api/world
================================================
FILE: cmd/certs/CA.key
================================================
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,3172865781AB630D
b65fTCSGMadtvR5GnVfTsssu9qrQgm5XNCXRJ+PLpuXuWdywORTOEM4/FtRA62AN
f41Dl/OO7nUNTTF4J+0fNiLMmnkiYvGSa5lcjRqBikk6waZjYkSUoo4TdGh502F/
jFzbGzqXlX0qt+pj0HY/fhKxpr45bOkBr9S3ucMbgyuREX1HShxF759Mc+sJFx7w
Ghs+byTL3cZABHRn/0xpC9S/PhEnsW3dPPJItCkEUsLumwTFCuaGskpP3chuVhZh
36p74An+Tg9wlgcaCaUSUHWs22rnLBHAjv9JzVuoLJWdUwIYrcQTJz2+8mVlun24
Qns7dhRafOHlIeOCxI/fmKlXE/S9S7tuXaMsHtqGK/22MAXJx/AOymjfgXfTLnF1
XmLK0FTyp9BB1pfW9P+D6JEp7bj1fRHZeCOkesJOjyDEb+v+oYMaha6IVfQCMKQf
P1+okMwBPGQIAr5d4Ov82mwpDyO1+rHAFn3b3zuro4rfHiHPDTo9Oa7EtMBIWZgj
Ln0KLeaRkPht6wSUubCSl8Ypg27xbHwbQbWcQsr/OwLgWMJ40/1QKaqYIDV4LytZ
mSzwwo4kQASKI1jwFWff+4yVqd3SuyW5uGcPNnkbneyKZzFHd/rAWnsT9cIIOX0v
US5LEn9r+qjYk+ZVCSgqmrwUHyPfbx/BeARJTDBzo2jJC+ZiR7UkzD54r5pzs7R4
9N3hVVmocS9BNPj45ioLw/Fhbu62N34NkRdDAisPhlQ+yM7klKBciQsEsKj1c6iw
QwJNScvgW6x0+47J+tp21KLuxdfVP+Bq9wSb2B4kR3l93/ZAXR1S0cg0cuez1frd
8g1kpQMzxhWVMzB6NHG7wAIo7p947mueP3Ggh1rgA6WLTltvH11ywadglG23HB7R
zuxcQqoPm7Tzcift316DE5Q+qipHDA2UmiWZ83ZVQCshiJxILsPkGbh7k+mRwRyh
e08TLEJMtCWiqvCmxHbebx7y8+oX035QIUVIOxHGier2CyZtgpaZyeEa4DHZCwuH
ZrTMfigGDSXnonCrVtsC9zSYGQav/tRVxahKsM/TA+O1gOWNTncEe2ESKWaBzJpQ
wUo7u/e/hbyPxMd2ezmeYVWwRSy9J/uZOsH0DsUlGsUzTWj+qrbmrdDYGVI2sgA1
xfTfv7vdLFpESDRV4eRWscblusHffjCFA9oC9Y9qj2M8X+HLa/EELMb5CCWGZm3E
HMZKwg6cah5rc82FFk0nDTzNpw57rTorPOGDe3oUif2NR+A4gxyepVrNDBFxDzbd
PT2YMPr2IsHgxnRwFyoAPGG3rKCO+rFNJKcfrxvdeTj7mZ0Yzo93f9ycgNhqDKnR
+n8vCWtWnRuDu8hk3d5YjhaZux/SmOuF9G6VX0jnJA534aLhEo0mzddGKM0bnOGN
J1aMc65s4rPqUOQiase/3w7fNDu6szF/tcUMEWPRCDymZpZ9yznK122ajQVrKyOq
HDcftg1rrKli8I/AljJMgC9ACZ9YfOfZ3qymcY7X7ZJyMucKi822ajIggi630aJQ
sEqwxl5coM/N+Rhcp2/NiYyq3MXQBibKhq00OBLq76QQeNsJbRhUDw==
-----END RSA PRIVATE KEY-----
================================================
FILE: cmd/certs/CA.pem
================================================
-----BEGIN CERTIFICATE-----
MIIDijCCAnICCQCImGKiTiq7ITANBgkqhkiG9w0BAQsFADCBhjELMAkGA1UEBhMC
SU4xCzAJBgNVBAgMAktBMRIwEAYDVQQHDAlCZW5nYWx1cnUxFTATBgNVBAoMDERl
bGl2ZXJ5SGVybzETMBEGA1UECwwKUS1Db21tZXJjZTESMBAGA1UEAwwJbG9jYWxo
b3N0MRYwFAYJKoZIhvcNAQkBFgdrQGIuY29tMB4XDTIyMTIwOTE2MDQxMFoXDTMy
MTIwNjE2MDQxMFowgYYxCzAJBgNVBAYTAklOMQswCQYDVQQIDAJLQTESMBAGA1UE
BwwJQmVuZ2FsdXJ1MRUwEwYDVQQKDAxEZWxpdmVyeUhlcm8xEzARBgNVBAsMClEt
Q29tbWVyY2UxEjAQBgNVBAMMCWxvY2FsaG9zdDEWMBQGCSqGSIb3DQEJARYHa0Bi
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM83cerEFzgRxu/l
NrFOGOCn1bwexXoP1xawzVaj/vmTL4+bN114e4KnJrmon9k9NHUxhJJGfNxM7cSG
7lYlIwIDdMt1FNm1iWjPycvcxWZppEnjbwlOGYn0miekr+SSh18AbCZ+kcZFx6Cp
O4wEb29kXCxInGEdgj5M23EpQdi5qXCmfmrIl/ueiNnJPQezFir8UizRKG5xHnZK
BaGcT0E4lOJLLKGqpvN5v0cO1Vwu2nxFmXlcV5dWsPOjxvPPSCYuu0EHhJ2jucQW
MNtc6cucG70rOJwkQi6JMbS5XU9pboux2O/H0WUwFSYjI40opWjKpXE5eo6GIgl/
eHCiNJ8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAH9a3WOMgGvDn80O8hvhZzXO8
6XPBDqjW7Zk/l1GiZYpNuDvAuqiBIDxKQZdRtnbRBTCLGO6yyHt974WStJud1/sN
Loam78+GMEMJq0tUNUNXuOVNLo/Zz/4tN2cDosnB8k+Atm+c3m5TSHaOayOy+PJL
OiDi7RP5IPpiEYtdGvE2eoYfqjSnY00kIV5ea57PIc3gkFO9FXP+M6UXzkxo2xC7
fvVhYEVjQ7uvdWLGKYMvF/PRV2OKRnAdFasga3k8PyC/ToxjN/87ypeUy1VZzEm7
3zsbislIKL36CCOYmGaUgTPfxXWN/MvUgb87lWfrqPbSM7ooSQGHFe1eP5Wd2g==
-----END CERTIFICATE-----
================================================
FILE: cmd/certs/CA.srl
================================================
FDC7B23B140FA79F
================================================
FILE: cmd/certs/localhost.crt
================================================
-----BEGIN CERTIFICATE-----
MIIEgjCCA2qgAwIBAgIJAP3HsjsUD6efMA0GCSqGSIb3DQEBCwUAMIGGMQswCQYD
VQQGEwJJTjELMAkGA1UECAwCS0ExEjAQBgNVBAcMCUJlbmdhbHVydTEVMBMGA1UE
CgwMRGVsaXZlcnlIZXJvMRMwEQYDVQQLDApRLUNvbW1lcmNlMRIwEAYDVQQDDAls
b2NhbGhvc3QxFjAUBgkqhkiG9w0BCQEWB2tAYi5jb20wHhcNMjIxMjA5MTYwOTQ2
WhcNMzIxMjA2MTYwOTQ2WjCBlzELMAkGA1UEBhMCREUxDzANBgNVBAgMBkJlcmxp
bjEWMBQGA1UEBwwNUmVpbmlja2VuZG9yZjEWMBQGA1UECgwNRGVsaXZlcnkgSGVy
bzETMBEGA1UECwwKUS1Db21tZXJjZTEZMBcGA1UEAwwQZGVsaXZlcnloZXJvLmNv
bTEXMBUGCSqGSIb3DQEJARYIa0Bibi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDRT6l39GZ3vSAi1eLt8oauseH4uNwijzcaqHot45f3087eqSej
hfqmTfhga+MSDtbKIo73O4wq12klCbTtil4UpRT4dVJwKQLXLriFCiq40Wzcyhqy
E0qGoZG1TCoy3PLUCwkxXlixAdEimhuZPIVPDQIY0fs1c8GxdFfhxMQ88WEqs0Rp
rygYp+hD18Hk0VYhPmqZXb0m3BG7/eTYHrYDVAdk9f2OYMR925idwk94iHvTjOqC
bOpVOzF4FM+jkT7r4hfa2UuCF4sYNhAP2DEZWHnoYjb7cxRDKYshMqCH5WhlbB+v
rAllLs+4GEu1yOs0VYqt2TzXNr/6KK1G77SRAgMBAAGjgd8wgdwwgaUGA1UdIwSB
nTCBmqGBjKSBiTCBhjELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAktBMRIwEAYDVQQH
DAlCZW5nYWx1cnUxFTATBgNVBAoMDERlbGl2ZXJ5SGVybzETMBEGA1UECwwKUS1D
b21tZXJjZTESMBAGA1UEAwwJbG9jYWxob3N0MRYwFAYJKoZIhvcNAQkBFgdrQGIu
Y29tggkAiJhiok4quyEwCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwGgYDVR0RBBMw
EYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQA2P0I8ujEPS2GB
7cMUAE3cB4fxEwI2t89JGcmeK0BUyVBlvdwZGEIVs2Tn0FFbs0VpcgeY3YkV2ogs
ekrUHQzmnRX9EtTMucGM6gX4JeDJWWthehVIB6Jp1iDLtAAbyCGph5nrdArkA0tR
ANkyrXKTcMAx3giBzSZrpxguF+fnASZ+p99c57FnRXjMv5NkQnSCgRQkmaHtUIKJ
dDAlEIyPrpfe2bbw7BtUt2UiW9KPz/CG9TDrpWzh5jRyoJRzXUhlOPgFPCBvH1AC
DGl2ciAGfScEY+HZp+YPdTzln5TSc4w/REuWDqBIydJwytUQX+EcTA0936TQq3ec
lYKaXexW
-----END CERTIFICATE-----
================================================
FILE: cmd/certs/localhost.csr
================================================
-----BEGIN CERTIFICATE REQUEST-----
MIIC3TCCAcUCAQAwgZcxCzAJBgNVBAYTAkRFMQ8wDQYDVQQIDAZCZXJsaW4xFjAU
BgNVBAcMDVJlaW5pY2tlbmRvcmYxFjAUBgNVBAoMDURlbGl2ZXJ5IEhlcm8xEzAR
BgNVBAsMClEtQ29tbWVyY2UxGTAXBgNVBAMMEGRlbGl2ZXJ5aGVyby5jb20xFzAV
BgkqhkiG9w0BCQEWCGtAYm4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA0U+pd/Rmd70gItXi7fKGrrHh+LjcIo83Gqh6LeOX99PO3qkno4X6pk34
YGvjEg7WyiKO9zuMKtdpJQm07YpeFKUU+HVScCkC1y64hQoquNFs3MoashNKhqGR
tUwqMtzy1AsJMV5YsQHRIpobmTyFTw0CGNH7NXPBsXRX4cTEPPFhKrNEaa8oGKfo
Q9fB5NFWIT5qmV29JtwRu/3k2B62A1QHZPX9jmDEfduYncJPeIh704zqgmzqVTsx
eBTPo5E+6+IX2tlLgheLGDYQD9gxGVh56GI2+3MUQymLITKgh+VoZWwfr6wJZS7P
uBhLtcjrNFWKrdk81za/+iitRu+0kQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEB
ADGo51Y/2/uJhndzitmBLM9yvwXmsD7JQvT3y+8xSne0p+jniwHhzqLww6SLLtIQ
LRUzbXQlnJnPzj2fhdPYM9238Cxyd/w59/cg/RXnkjMnoaiH/9FZpqwIwnMFugkp
+BcqszZat70OjdhZPkI/WzImNHdtSzUFhI3OACXqhdSM2wGkzHQCWxMzRmKsE8XF
iMmuvFVBExXLoG/PqoRS5W3Op1SZdYvKybhmrgM+XeHlvTv8VOFgUBmBEolhZtvU
9eT3ommzMbyZSbf6eXlTj+OrBTlN7n8et42TyDaf03kZhhiSpiq+lNz7+eHcF64Z
8u4AOXi22aPkwy2fffYKr4E=
-----END CERTIFICATE REQUEST-----
================================================
FILE: cmd/certs/localhost.decrypted.key
================================================
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0U+pd/Rmd70gItXi7fKGrrHh+LjcIo83Gqh6LeOX99PO3qkn
o4X6pk34YGvjEg7WyiKO9zuMKtdpJQm07YpeFKUU+HVScCkC1y64hQoquNFs3Moa
shNKhqGRtUwqMtzy1AsJMV5YsQHRIpobmTyFTw0CGNH7NXPBsXRX4cTEPPFhKrNE
aa8oGKfoQ9fB5NFWIT5qmV29JtwRu/3k2B62A1QHZPX9jmDEfduYncJPeIh704zq
gmzqVTsxeBTPo5E+6+IX2tlLgheLGDYQD9gxGVh56GI2+3MUQymLITKgh+VoZWwf
r6wJZS7PuBhLtcjrNFWKrdk81za/+iitRu+0kQIDAQABAoIBAQCwHW5Df0HkiB6N
ERiTC9ilDwlKxQh8j7JW3OGI0RJiNTbABOZUYfwHiF1vi/eQjynNBIz0m4cR2RQg
VO2GXUFR76EYeWb29prsQeSCFI7j2VrW37rckPzJERNPz5lGGMC9B9ghUPghX5z/
l1mXcuPcIt7b0XqkfBTC4li7n1taxluvKpxtFoN7XuV7QyhpQtR74M9CAmmtxAjY
3HSY3fC7kSbT+8mii29stm3/zJSdnuSdlfrHRv+cXsrWfN5isjpvDb7HCcvm7OHB
QyUC89zy/WM8xReKEStkRX+/+zW2//y7aXMzI/YmiPdX+XoIY+MdxqvMjzbATZF+
haBeM5ttAoGBAP5GhhmuvKSURJTfoEroIxtXnfpvE4T/+TQmR1Wc9m/Ecc6iVuHF
RgHpkKhOFl5P457Y1oRpYkmECxZ4KuvHwIqe9rRofGByxbxilvA69p8RkV8oI/gc
qP5+VN2Sx77tTTKVX7S6SFrROSmB9YAE7/83Vo1oD9absl47SeR9+EBbAoGBANK7
EhIYE12q5nQBDhjjAt9Xa6N+ROjonjW1cWq/v6FWgoBXiQtsh1MSK2KZPi7F05kP
9oYM//4oMy/mFsKbS8HTy+Y7HDYvh7Gz3gAxUWrabIFrJlJMXMj9VIgYDZsnQiBD
3j2sUOpr+5B4laHD5oM8rYjR10F1AN7oAuTdDzKDAoGAfmkHH9t70wIW+kAWi0bO
tTggxLDV7mfnNyLUkd5fsX7i6UxRjxoozKiWDuYLPsXOrli0hM1zXIL1lC0XgXIj
6YZPta7ALp7AaQBGc5WMp9XvBHSLNTziUurxO9pNzUBiAYS7OLjnYabkGRuPth4+
Rg33zILwZMuwqCIngR2S/kMCgYB9ssCQsnO6x5o3T/nMtnycJFU8bLFGDJtyhgxl
FIOGBUhKrew9ODtwPcJLSgVhePdCsdbnFxIL1IbT53dkFaYWs/NIHbIyUB+szBF8
I+7gwfE/MV7mcE5YRWQK2e4jwkMbY+BJAWQysL6Z6pO2rlftqGAK4MB5dwVR8Sro
wUOzaQKBgQDZ3ohj61drnkkdRRRX5115ne3AMctx1rn7Ss6AByDn21wYUrOP0qAy
i4kmah0kibqz8+N6PM7G7gA5uXHk0bwFfgU/TPQdz6DfzSbTmO7lEEJ7ejI9ml0C
1Y16Rya8Ny3fLjCFWKYReFhXUWNzuWPHsQezUiwnMeHRka0c9QFVSA==
-----END RSA PRIVATE KEY-----
================================================
FILE: cmd/certs/localhost.ext
================================================
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 127.0.0.1
================================================
FILE: cmd/certs/localhost.key
================================================
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,9D6364828FCEE2E0
2RhK7oE6kVWBNDFGjRUuxPO5XI3qarcJTswXNk7YFqmyQpgt9cFXUdRI36Ppbpcs
Sw6h2gmBaqsS7sbL3wWZyZL+PcVVCy7TRTBI/mU1Cvj1iRe9ms3+b7uwnuSGh0DF
G40X+VACjwmP+hAKQivcPtog+c5laAx/QQ456kNQMl9yYCdbJCTCBNKVS8ycIB54
0moAxCv8lgO0mllh4vvMl5RbfOqPVr0Ky8EWJJqs9CWCy7eKLe8BIzy7nDGcVhx+
BZ24r8h59OIUWCFmJ/FXhDTLXrPr2xpF0COTKuPrTtU7LMaCdYljyilfkCo4l+Pk
ctawRwKLUd4/IE26P90WaoV85y9tne1145pcKQVQa0NdfI6rRA2jRqzv6iFrVYBM
AUeybrX+HnaHABwJYNunXBCk3YUWk5NuMPsTNGlPqMPQyVC/D196YSU16sDx2zlT
Z886GqLfr5SqwInvlb7zQn72+1beoh2Du7NQ6oXuVZsc5w1sGiv7B7axOEznJ8KT
QxTst9pCLj2zpm45fRW8cKscC9g/fXMYQrRwcSeeuWvXtI9csljpWKpolvepKFmx
bUrYUIqFyfCZsLBHaXf3vsbPF/h3q0ZGDadVTWioiA9jpi+y5rcb8mFDl9qiRBsC
RDafd0aoJPXiWotO58Fo4VXRLWqEUhyHF9oVN5SD5CWSWos/YfkRXAVmElEK+dbM
TpumlQvt6gbXculag7c/OtHJtVfLuhqdW4yGXvwY1z07U+EdYLrMomKffYaxjTi5
2c1bNA7t2cF4mQk++DH9jvj1Xfuxdh5p84g1tvSYrVvJWt4JHzbzknylteGMSJ5w
dkuDXdBS9qrwjvc6nLNAn+qXduzAyzf2GR5obe/mFwZSvm33o6W/DFWkhQDdHjSC
o7QuyNvHYYMCau7CKcpgoBycBrkktQ6gFAGR1HsT4Xdsk60XkLi8ctUiLPdFL6zc
tRcmZt+ddzJA2qaNCxNopj2J8Qkav208zQ9f7P6DZNe3pWI09SrUisnAb0cuYASm
XfAcBP9vBfKx9pvxfpVgp6DYFaVllgXDM+Z1lmKzZMOAb0RFcyWsYtILIgLnTDeG
tMeHygAevAAT1N1uKlQMvXvOtmehOGVfBAaJVt1U4xTWjCxBGfWed+bs9NkL2zSU
0/kEacWEbq0c23Wsiv8IHKxqZLPyeiyHvZgDWLt4v1Y+t9l+q+HZIQxXmFhb96qt
+tOv7pY6dig2fLRAApp9Q2kKfimQBtO7sxafK3qBmT75Y86gBdmSgjREviec9Bb2
VeZeqf/TPhiBVbRpYAe4RixF5cal3pxC9N9GmEMk+Qfr/MEonkUgX8w4tlP3hkXx
HS+5GpltrIag5GEaKbFgX/FYcHbDQNMXmIqV6ieavN+04MbHuC4Cm1joHVTEQJUn
II0mfCAgxFOYKqr2Sex86zxhury5O1oYS0ETCTCLVgab1dIin4+cElrGW42Sw3Bm
tGChrevGYi2hBQkBOHfnw2lFLtJmQ0R0pke+CICVWLriTV5N183f5zosdjJyYpxa
JL7p/ePLjLRCvMpY185/mbvc8h1AtIsm6N67r8xK250oZBid9LZXa6G449RnPku7
-----END RSA PRIVATE KEY-----
================================================
FILE: cmd/handlers.go
================================================
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"strings"
"github.com/naughtygopher/webgo/v7"
"github.com/naughtygopher/webgo/v7/extensions/sse"
)
// StaticFilesHandler is used to serve static files
func StaticFilesHandler(rw http.ResponseWriter, r *http.Request) {
wctx := webgo.Context(r)
// '..' is replaced to prevent directory traversal which could go out of static directory
path := strings.ReplaceAll(wctx.Params()["w"], "..", "-")
path = strings.ReplaceAll(path, "~", "-")
rw.Header().Set("Last-Modified", lastModified)
http.ServeFile(rw, r, fmt.Sprintf("./static/%s", path))
}
func OriginalResponseWriterHandler(w http.ResponseWriter, r *http.Request) {
rw := webgo.OriginalResponseWriter(w)
if rw == nil {
webgo.Send(w, "text/html", "got nil", http.StatusPreconditionFailed)
return
}
webgo.Send(w, "text/html", "success", http.StatusOK)
}
func HomeHandler(w http.ResponseWriter, r *http.Request) {
fs, err := os.OpenFile("./static/index.html", os.O_RDONLY, 0600)
if err != nil {
webgo.SendError(w, err.Error(), http.StatusInternalServerError)
return
}
info, err := fs.Stat()
if err != nil {
webgo.SendError(w, err.Error(), http.StatusInternalServerError)
return
}
out := make([]byte, info.Size())
_, err = fs.Read(out)
if err != nil {
webgo.SendError(w, err.Error(), http.StatusInternalServerError)
return
}
pushHomepage(r, w)
_, _ = w.Write(out)
}
func pushCSS(pusher http.Pusher, r *http.Request, path string) {
cssOpts := &http.PushOptions{
Header: http.Header{
"Accept-Encoding": r.Header["Accept-Encoding"],
"Content-Type": []string{"text/css; charset=UTF-8"},
},
}
err := pusher.Push(path, cssOpts)
if err != nil {
webgo.LOGHANDLER.Error(err)
}
}
func pushJS(pusher http.Pusher, r *http.Request, path string) {
cssOpts := &http.PushOptions{
Header: http.Header{
"Accept-Encoding": r.Header["Accept-Encoding"],
"Content-Type": []string{"application/javascript"},
},
}
err := pusher.Push(path, cssOpts)
if err != nil {
webgo.LOGHANDLER.Error(err)
}
}
func pushHomepage(r *http.Request, w http.ResponseWriter) {
pusher, ok := w.(http.Pusher)
if !ok {
return
}
cp, _ := r.Cookie("pusher")
if cp != nil {
return
}
cookie := &http.Cookie{
Name: "pusher",
Value: "css,js",
MaxAge: 300,
}
http.SetCookie(w, cookie)
pushCSS(pusher, r, "/static/css/main.css")
pushCSS(pusher, r, "/static/css/normalize.css")
pushJS(pusher, r, "/static/js/main.js")
pushJS(pusher, r, "/static/js/sse.js")
}
func SSEHandler(sse *sse.SSE) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
params := webgo.Context(r).Params()
r.Header.Set(sse.ClientIDHeader, params["clientID"])
err := sse.Handler(w, r)
if err != nil && !errors.Is(err, context.Canceled) {
log.Println("errorLogger:", err.Error())
return
}
}
}
func ErrorSetterHandler(w http.ResponseWriter, r *http.Request) {
err := errors.New("oh no, server error")
webgo.SetError(r, err)
webgo.R500(w, err.Error())
}
func ParamHandler(w http.ResponseWriter, r *http.Request) {
// WebGo context
wctx := webgo.Context(r)
// URI parameters, map[string]string
params := wctx.Params()
// route, the webgo.Route which is executing this request
route := wctx.Route
webgo.R200(
w,
map[string]interface{}{
"route_name": route.Name,
"route_pattern": route.Pattern,
"params": params,
"chained": r.Header.Get("chained"),
},
)
}
func InvalidJSONHandler(w http.ResponseWriter, r *http.Request) {
webgo.R200(w, make(chan int))
}
================================================
FILE: cmd/main.go
================================================
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
"github.com/naughtygopher/webgo/v7"
"github.com/naughtygopher/webgo/v7/extensions/sse"
"github.com/naughtygopher/webgo/v7/middleware/accesslog"
"github.com/naughtygopher/webgo/v7/middleware/cors"
)
var (
lastModified = time.Now().Format(http.TimeFormat)
)
func chain(w http.ResponseWriter, r *http.Request) {
r.Header.Set("chained", "true")
}
// errLogger is a middleware which will log all errors returned/set by a handler
func errLogger(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
next(w, r)
err := webgo.GetError(r)
if err != nil {
// log only server errors
if webgo.ResponseStatus(w) > 499 {
log.Println("errorLogger:", err.Error())
}
}
}
func routegroupMiddleware(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
w.Header().Add("routegroup", "true")
next(w, r)
}
func getRoutes(sse *sse.SSE) []*webgo.Route {
return []*webgo.Route{
{
Name: "root",
Method: http.MethodGet,
Pattern: "/",
Handlers: []http.HandlerFunc{HomeHandler},
TrailingSlash: true,
},
{
Name: "matchall",
Method: http.MethodGet,
Pattern: "/matchall/:wildcard*",
Handlers: []http.HandlerFunc{ParamHandler},
TrailingSlash: true,
},
{
Name: "api",
Method: http.MethodGet,
Pattern: "/api/:param",
Handlers: []http.HandlerFunc{chain, ParamHandler},
TrailingSlash: true,
FallThroughPostResponse: true,
},
{
Name: "invalidjson",
Method: http.MethodGet,
Pattern: "/invalidjson",
Handlers: []http.HandlerFunc{InvalidJSONHandler},
TrailingSlash: true,
},
{
Name: "error-setter",
Method: http.MethodGet,
Pattern: "/error-setter",
Handlers: []http.HandlerFunc{ErrorSetterHandler},
TrailingSlash: true,
},
{
Name: "original-responsewriter",
Method: http.MethodGet,
Pattern: "/original-responsewriter",
Handlers: []http.HandlerFunc{OriginalResponseWriterHandler},
TrailingSlash: true,
},
{
Name: "static",
Method: http.MethodGet,
Pattern: "/static/:w*",
Handlers: []http.HandlerFunc{StaticFilesHandler},
TrailingSlash: true,
},
{
Name: "sse",
Method: http.MethodGet,
Pattern: "/sse/:clientID",
Handlers: []http.HandlerFunc{SSEHandler(sse)},
TrailingSlash: true,
},
}
}
func setup() (*webgo.Router, *sse.SSE) {
port := strings.TrimSpace(os.Getenv("HTTP_PORT"))
if port == "" {
port = "8080"
}
cfg := &webgo.Config{
Host: "",
Port: port,
HTTPSPort: "9595",
ReadTimeout: 15 * time.Second,
WriteTimeout: 1 * time.Hour,
CertFile: "./certs/localhost.crt",
KeyFile: "./certs/localhost.decrypted.key",
}
webgo.GlobalLoggerConfig(
nil, nil,
webgo.LogCfgDisableDebug,
)
routeGroup := webgo.NewRouteGroup("/v7.0.0", false)
routeGroup.Add(webgo.Route{
Name: "router-group-prefix-v7.0.0_api",
Method: http.MethodGet,
Pattern: "/api/:param",
Handlers: []http.HandlerFunc{chain, ParamHandler},
})
routeGroup.Use(routegroupMiddleware)
sseService := sse.New()
sseService.OnRemoveClient = func(ctx context.Context, clientID string, count int) {
log.Printf("\nClient %q removed, active client(s): %d\n", clientID, count)
}
sseService.OnCreateClient = func(ctx context.Context, client *sse.Client, count int) {
log.Printf("\nClient %q added, active client(s): %d\n", client.ID, count)
}
routes := getRoutes(sseService)
routes = append(routes, routeGroup.Routes()...)
router := webgo.NewRouter(cfg, routes...)
router.UseOnSpecialHandlers(accesslog.AccessLog)
router.Use(
errLogger,
cors.CORS(nil),
accesslog.AccessLog,
)
return router, sseService
}
func main() {
router, sseService := setup()
clients := []*sse.Client{}
sseService.OnCreateClient = func(ctx context.Context, client *sse.Client, count int) {
clients = append(clients, client)
}
// broadcast server time to all SSE listeners
go func() {
retry := time.Millisecond * 500
for {
now := time.Now().Format(time.RFC1123Z)
sseService.Broadcast(sse.Message{
Data: now + fmt.Sprintf(" (%d)", sseService.ActiveClients()),
Retry: retry,
})
time.Sleep(time.Second)
}
}()
go router.StartHTTPS()
router.Start()
}
================================================
FILE: cmd/static/css/main.css
================================================
* {
transition: color 0.25s, margin 0.25s, padding 0.25s, width 0.25s, height 0.25s, background-color 0.25s;
}
html, body {
font-family: sans-serif;
font-size: 16px;
line-height: 1.5em;
font-weight: 400;
background: #efefef;
color: #444;
}
p {
margin: 0 0 1em;
}
a {
color: #999;
}
a:hover {
color: #222;
}
section.main {
background: #fff;
width: 90%;
max-width: 370px;
margin: 10vw auto;
padding: 0 2em;
border-radius: 4px;
overflow: hidden;
}
table {
width: 100%;
font-size: 12px;
line-height: 1.5em;
border: 1px solid #eee;
border-collapse: collapse;
}
tr, td {
border: 1px solid #eee;
}
td {
padding: 0.25rem;
text-align: right;
}
td:nth-child(1) {
text-align: left;
background-color: rgba(0,0,0,0.02);
}
================================================
FILE: cmd/static/css/normalize.css
================================================
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}
================================================
FILE: cmd/static/index.html
================================================
Webgo - Sample