updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Paul 2024-04-28 13:08:41 +02:00
parent d7738f79e1
commit fa74f7ad4c
422 changed files with 4629 additions and 4353 deletions

View File

@ -1,5 +1,77 @@
# Changelog
## v4.12.0 - 2024-04-15
**Security**
* Update golang.org/x/net dep because of [GO-2024-2687](https://pkg.go.dev/vuln/GO-2024-2687) by @aldas in https://github.com/labstack/echo/pull/2625
**Enhancements**
* binder: make binding to Map work better with string destinations by @aldas in https://github.com/labstack/echo/pull/2554
* README.md: add Encore as sponsor by @marcuskohlberg in https://github.com/labstack/echo/pull/2579
* Reorder paragraphs in README.md by @aldas in https://github.com/labstack/echo/pull/2581
* CI: upgrade actions/checkout to v4 by @aldas in https://github.com/labstack/echo/pull/2584
* Remove default charset from 'application/json' Content-Type header by @doortts in https://github.com/labstack/echo/pull/2568
* CI: Use Go 1.22 by @aldas in https://github.com/labstack/echo/pull/2588
* binder: allow binding to a nil map by @georgmu in https://github.com/labstack/echo/pull/2574
* Add Skipper Unit Test In BasicBasicAuthConfig and Add More Detail Explanation regarding BasicAuthValidator by @RyoKusnadi in https://github.com/labstack/echo/pull/2461
* fix some typos by @teslaedison in https://github.com/labstack/echo/pull/2603
* fix: some typos by @pomadev in https://github.com/labstack/echo/pull/2596
* Allow ResponseWriters to unwrap writers when flushing/hijacking by @aldas in https://github.com/labstack/echo/pull/2595
* Add SPDX licence comments to files. by @aldas in https://github.com/labstack/echo/pull/2604
* Upgrade deps by @aldas in https://github.com/labstack/echo/pull/2605
* Change type definition blocks to single declarations. This helps copy… by @aldas in https://github.com/labstack/echo/pull/2606
* Fix Real IP logic by @cl-bvl in https://github.com/labstack/echo/pull/2550
* Default binder can use `UnmarshalParams(params []string) error` inter… by @aldas in https://github.com/labstack/echo/pull/2607
* Default binder can bind pointer to slice as struct field. For example `*[]string` by @aldas in https://github.com/labstack/echo/pull/2608
* Remove maxparam dependence from Context by @aldas in https://github.com/labstack/echo/pull/2611
* When route is registered with empty path it is normalized to `/`. by @aldas in https://github.com/labstack/echo/pull/2616
* proxy middleware should use httputil.ReverseProxy for SSE requests by @aldas in https://github.com/labstack/echo/pull/2624
## v4.11.4 - 2023-12-20
**Security**
* Upgrade golang.org/x/crypto to v0.17.0 to fix vulnerability [issue](https://pkg.go.dev/vuln/GO-2023-2402) [#2562](https://github.com/labstack/echo/pull/2562)
**Enhancements**
* Update deps and mark Go version to 1.18 as this is what golang.org/x/* use [#2563](https://github.com/labstack/echo/pull/2563)
* Request logger: add example for Slog https://pkg.go.dev/log/slog [#2543](https://github.com/labstack/echo/pull/2543)
## v4.11.3 - 2023-11-07
**Security**
* 'c.Attachment' and 'c.Inline' should escape filename in 'Content-Disposition' header to avoid 'Reflect File Download' vulnerability. [#2541](https://github.com/labstack/echo/pull/2541)
**Enhancements**
* Tests: refactor context tests to be separate functions [#2540](https://github.com/labstack/echo/pull/2540)
* Proxy middleware: reuse echo request context [#2537](https://github.com/labstack/echo/pull/2537)
* Mark unmarshallable yaml struct tags as ignored [#2536](https://github.com/labstack/echo/pull/2536)
## v4.11.2 - 2023-10-11
**Security**
* Bump golang.org/x/net to prevent CVE-2023-39325 / CVE-2023-44487 HTTP/2 Rapid Reset Attack [#2527](https://github.com/labstack/echo/pull/2527)
* fix(sec): randomString bias introduced by #2490 [#2492](https://github.com/labstack/echo/pull/2492)
* CSRF/RequestID mw: switch math/random usage to crypto/random [#2490](https://github.com/labstack/echo/pull/2490)
**Enhancements**
* Delete unused context in body_limit.go [#2483](https://github.com/labstack/echo/pull/2483)
* Use Go 1.21 in CI [#2505](https://github.com/labstack/echo/pull/2505)
* Fix some typos [#2511](https://github.com/labstack/echo/pull/2511)
* Allow CORS middleware to send Access-Control-Max-Age: 0 [#2518](https://github.com/labstack/echo/pull/2518)
* Bump dependancies [#2522](https://github.com/labstack/echo/pull/2522)
## v4.11.1 - 2023-07-16
**Fixes**

View File

@ -31,6 +31,6 @@ benchmark: ## Run benchmarks
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
goversion ?= "1.17"
test_version: ## Run tests inside Docker with given version (defaults to 1.17 oldest supported). Example: make test_version goversion=1.17
goversion ?= "1.19"
test_version: ## Run tests inside Docker with given version (defaults to 1.19 oldest supported). Example: make test_version goversion=1.19
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"

View File

@ -3,26 +3,24 @@
[![Sourcegraph](https://sourcegraph.com/github.com/labstack/echo/-/badge.svg?style=flat-square)](https://sourcegraph.com/github.com/labstack/echo?badge)
[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/labstack/echo/v4)
[![Go Report Card](https://goreportcard.com/badge/github.com/labstack/echo?style=flat-square)](https://goreportcard.com/report/github.com/labstack/echo)
[![Build Status](http://img.shields.io/travis/labstack/echo.svg?style=flat-square)](https://travis-ci.org/labstack/echo)
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/labstack/echo/echo.yml?style=flat-square)](https://github.com/labstack/echo/actions)
[![Codecov](https://img.shields.io/codecov/c/github/labstack/echo.svg?style=flat-square)](https://codecov.io/gh/labstack/echo)
[![Forum](https://img.shields.io/badge/community-forum-00afd1.svg?style=flat-square)](https://github.com/labstack/echo/discussions)
[![Twitter](https://img.shields.io/badge/twitter-@labstack-55acee.svg?style=flat-square)](https://twitter.com/labstack)
[![License](http://img.shields.io/badge/license-mit-blue.svg?style=flat-square)](https://raw.githubusercontent.com/labstack/echo/master/LICENSE)
## Supported Go versions
## Echo
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with
older versions.
High performance, extensible, minimalist Go web framework.
As of version 4.0.0, Echo is available as a [Go module](https://github.com/golang/go/wiki/Modules).
Therefore a Go version capable of understanding /vN suffixed imports is required:
* [Official website](https://echo.labstack.com)
* [Quick start](https://echo.labstack.com/docs/quick-start)
* [Middlewares](https://echo.labstack.com/docs/category/middleware)
Any of these versions will allow you to import Echo as `github.com/labstack/echo/v4` which is the recommended
way of using Echo going forward.
Help and questions: [Github Discussions](https://github.com/labstack/echo/discussions)
For older versions, please use the latest v3 tag.
## Feature Overview
### Feature Overview
- Optimized HTTP router which smartly prioritize routes
- Build robust and scalable RESTful APIs
@ -38,6 +36,18 @@ For older versions, please use the latest v3 tag.
- Automatic TLS via Lets Encrypt
- HTTP/2 support
## Sponsors
<div>
<a href="https://encore.dev" style="display: inline-flex; align-items: center; gap: 10px">
<img src="https://user-images.githubusercontent.com/78424526/214602214-52e0483a-b5fc-4d4c-b03e-0b7b23e012df.svg" height="28px" alt="encore icon"></img>
<b>Encore the platform for building Go-based cloud backends</b>
</a>
</div>
<br/>
Click [here](https://github.com/sponsors/labstack) for more information on sponsorship.
## Benchmarks
Date: 2020/11/11<br>
@ -57,6 +67,7 @@ The benchmarks above were run on an Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
// go get github.com/labstack/echo/{version}
go get github.com/labstack/echo/v4
```
Latest version of Echo supports last four Go major [releases](https://go.dev/doc/devel/release) and might work with older versions.
### Example
@ -117,10 +128,6 @@ of middlewares in this list.
Please send a PR to add your own library here.
## Help
- [Forum](https://github.com/labstack/echo/discussions)
## Contribute
**Use issues for everything**

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
@ -11,23 +14,28 @@ import (
"strings"
)
type (
// Binder is the interface that wraps the Bind method.
Binder interface {
type Binder interface {
Bind(i interface{}, c Context) error
}
// DefaultBinder is the default implementation of the Binder interface.
DefaultBinder struct{}
type DefaultBinder struct{}
// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
// Types that don't implement this, but do implement encoding.TextUnmarshaler
// will use that interface instead.
BindUnmarshaler interface {
type BindUnmarshaler interface {
// UnmarshalParam decodes and assigns a value from an form or query param.
UnmarshalParam(param string) error
}
)
// bindMultipleUnmarshaler is used by binder to unmarshal multiple values from request at once to
// type implementing this interface. For example request could have multiple query fields `?a=1&a=2&b=test` in that case
// for `a` following slice `["1", "2"] will be passed to unmarshaller.
type bindMultipleUnmarshaler interface {
UnmarshalParams(params []string) error
}
// BindPathParams binds path params to bindable object
func (b *DefaultBinder) BindPathParams(c Context, i interface{}) error {
@ -131,10 +139,29 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
typ := reflect.TypeOf(destination).Elem()
val := reflect.ValueOf(destination).Elem()
// Map
if typ.Kind() == reflect.Map {
// Support binding to limited Map destinations:
// - map[string][]string,
// - map[string]string <-- (binds first value from data slice)
// - map[string]interface{}
// You are better off binding to struct but there are user who want this map feature. Source of data for these cases are:
// params,query,header,form as these sources produce string values, most of the time slice of strings, actually.
if typ.Kind() == reflect.Map && typ.Key().Kind() == reflect.String {
k := typ.Elem().Kind()
isElemInterface := k == reflect.Interface
isElemString := k == reflect.String
isElemSliceOfStrings := k == reflect.Slice && typ.Elem().Elem().Kind() == reflect.String
if !(isElemSliceOfStrings || isElemString || isElemInterface) {
return nil
}
if val.IsNil() {
val.Set(reflect.MakeMap(typ))
}
for k, v := range data {
if isElemString {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v[0]))
} else {
val.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
}
}
return nil
}
@ -161,14 +188,14 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
}
structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get(tag)
if typeField.Anonymous && structField.Kind() == reflect.Struct && inputFieldName != "" {
if typeField.Anonymous && structFieldKind == reflect.Struct && inputFieldName != "" {
// if anonymous struct with query/param/form tags, report an error
return errors.New("query/param/form tags are not allowed with anonymous struct field")
}
if inputFieldName == "" {
// If tag is nil, we inspect if the field is a not BindUnmarshaler struct and try to bind data into it (might contains fields with tags).
// structs that implement BindUnmarshaler are binded only when they have explicit tag
// structs that implement BindUnmarshaler are bound only when they have explicit tag
if _, ok := structField.Addr().Interface().(BindUnmarshaler); !ok && structFieldKind == reflect.Struct {
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
return err
@ -197,27 +224,46 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
continue
}
// Call this first, in case we're dealing with an alias to an array type
if ok, err := unmarshalField(typeField.Type.Kind(), inputValue[0], structField); ok {
// NOTE: algorithm here is not particularly sophisticated. It probably does not work with absurd types like `**[]*int`
// but it is smart enough to handle niche cases like `*int`,`*[]string`,`[]*int` .
// try unmarshalling first, in case we're dealing with an alias to an array type
if ok, err := unmarshalInputsToField(typeField.Type.Kind(), inputValue, structField); ok {
if err != nil {
return err
}
continue
}
numElems := len(inputValue)
if structFieldKind == reflect.Slice && numElems > 0 {
if ok, err := unmarshalInputToField(typeField.Type.Kind(), inputValue[0], structField); ok {
if err != nil {
return err
}
continue
}
// we could be dealing with pointer to slice `*[]string` so dereference it. There are wierd OpenAPI generators
// that could create struct fields like that.
if structFieldKind == reflect.Pointer {
structFieldKind = structField.Elem().Kind()
structField = structField.Elem()
}
if structFieldKind == reflect.Slice {
sliceOf := structField.Type().Elem().Kind()
numElems := len(inputValue)
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
for j := 0; j < numElems; j++ {
if err := setWithProperType(sliceOf, inputValue[j], slice.Index(j)); err != nil {
return err
}
}
val.Field(i).Set(slice)
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
return err
structField.Set(slice)
continue
}
if err := setWithProperType(structFieldKind, inputValue[0], structField); err != nil {
return err
}
}
return nil
@ -225,7 +271,7 @@ func (b *DefaultBinder) bindData(destination interface{}, data map[string][]stri
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
// But also call it here, in case we're dealing with an array of BindUnmarshalers
if ok, err := unmarshalField(valueKind, val, structField); ok {
if ok, err := unmarshalInputToField(valueKind, val, structField); ok {
return err
}
@ -266,35 +312,41 @@ func setWithProperType(valueKind reflect.Kind, val string, structField reflect.V
return nil
}
func unmarshalField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
switch valueKind {
case reflect.Ptr:
return unmarshalFieldPtr(val, field)
default:
return unmarshalFieldNonPtr(val, field)
func unmarshalInputsToField(valueKind reflect.Kind, values []string, field reflect.Value) (bool, error) {
if valueKind == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
field = field.Elem()
}
func unmarshalFieldNonPtr(value string, field reflect.Value) (bool, error) {
fieldIValue := field.Addr().Interface()
if unmarshaler, ok := fieldIValue.(BindUnmarshaler); ok {
return true, unmarshaler.UnmarshalParam(value)
unmarshaler, ok := fieldIValue.(bindMultipleUnmarshaler)
if !ok {
return false, nil
}
if unmarshaler, ok := fieldIValue.(encoding.TextUnmarshaler); ok {
return true, unmarshaler.UnmarshalText([]byte(value))
return true, unmarshaler.UnmarshalParams(values)
}
func unmarshalInputToField(valueKind reflect.Kind, val string, field reflect.Value) (bool, error) {
if valueKind == reflect.Ptr {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
field = field.Elem()
}
fieldIValue := field.Addr().Interface()
switch unmarshaler := fieldIValue.(type) {
case BindUnmarshaler:
return true, unmarshaler.UnmarshalParam(val)
case encoding.TextUnmarshaler:
return true, unmarshaler.UnmarshalText([]byte(val))
}
return false, nil
}
func unmarshalFieldPtr(value string, field reflect.Value) (bool, error) {
if field.IsNil() {
// Initialize the pointer to a nil value
field.Set(reflect.New(field.Type().Elem()))
}
return unmarshalFieldNonPtr(value, field.Elem())
}
func setIntField(value string, bitSize int, field reflect.Value) error {
if value == "" {
value = "0"

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
@ -1323,7 +1326,7 @@ func (b *ValueBinder) unixTime(sourceParam string, dest *time.Time, valueMustExi
case time.Second:
*dest = time.Unix(n, 0)
case time.Millisecond:
*dest = time.Unix(n/1e3, (n%1e3)*1e6) // TODO: time.UnixMilli(n) exists since Go1.17 switch to that when min version allows
*dest = time.UnixMilli(n)
case time.Nanosecond:
*dest = time.Unix(0, n)
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
@ -13,10 +16,9 @@ import (
"sync"
)
type (
// Context represents the context of the current HTTP request. It holds request and
// response objects, path, path parameters, data and registered handler.
Context interface {
type Context interface {
// Request returns `*http.Request`.
Request() *http.Request
@ -197,20 +199,33 @@ type (
Reset(r *http.Request, w http.ResponseWriter)
}
context struct {
type context struct {
request *http.Request
response *Response
path string
pnames []string
pvalues []string
query url.Values
handler HandlerFunc
store Map
echo *Echo
logger Logger
store Map
lock sync.RWMutex
// following fields are set by Router
// path is route path that Router matched. It is empty string where there is no route match.
// Route registered with RouteNotFound is considered as a match and path therefore is not empty.
path string
// pnames length is tied to param count for the matched route
pnames []string
// Usually echo.Echo is sizing pvalues but there could be user created middlewares that decide to
// overwrite parameter by calling SetParamNames + SetParamValues.
// When echo.Echo allocated that slice it length/capacity is tied to echo.Echo.maxParam value.
//
// It is important that pvalues size is always equal or bigger to pnames length.
pvalues []string
handler HandlerFunc
}
)
const (
// ContextKeyHeaderAllow is set by Router for getting value for `Allow` header in later stages of handler call chain.
@ -329,13 +344,9 @@ func (c *context) SetParamNames(names ...string) {
c.pnames = names
l := len(names)
if *c.echo.maxParam < l {
*c.echo.maxParam = l
}
if len(c.pvalues) < l {
// Keeping the old pvalues just for backward compatibility, but it sounds that doesn't make sense to keep them,
// probably those values will be overriden in a Context#SetParamValues
// probably those values will be overridden in a Context#SetParamValues
newPvalues := make([]string, l)
copy(newPvalues, c.pvalues)
c.pvalues = newPvalues
@ -347,11 +358,11 @@ func (c *context) ParamValues() []string {
}
func (c *context) SetParamValues(values ...string) {
// NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam at all times
// NOTE: Don't just set c.pvalues = values, because it has to have length c.echo.maxParam (or bigger) at all times
// It will brake the Router#Find code
limit := len(values)
if limit > *c.echo.maxParam {
limit = *c.echo.maxParam
if limit > len(c.pvalues) {
c.pvalues = make([]string, limit)
}
for i := 0; i < limit; i++ {
c.pvalues[i] = values[i]
@ -489,7 +500,7 @@ func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error
}
func (c *context) json(code int, i interface{}, indent string) error {
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
c.writeContentType(MIMEApplicationJSON)
c.response.Status = code
return c.echo.JSONSerializer.Serialize(c, i, indent)
}
@ -507,7 +518,7 @@ func (c *context) JSONPretty(code int, i interface{}, indent string) (err error)
}
func (c *context) JSONBlob(code int, b []byte) (err error) {
return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
return c.Blob(code, MIMEApplicationJSON, b)
}
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
@ -584,8 +595,10 @@ func (c *context) Inline(file, name string) error {
return c.contentDisposition(file, name, "inline")
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func (c *context) contentDisposition(file, name, dispositionType string) error {
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name)))
return c.File(file)
}
@ -640,8 +653,8 @@ func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
c.path = ""
c.pnames = nil
c.logger = nil
// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
for i := 0; i < *c.echo.maxParam; i++ {
// NOTE: Don't reset because it has to have length c.echo.maxParam (or bigger) at all times
for i := 0; i < len(c.pvalues); i++ {
c.pvalues[i] = ""
}
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
/*
Package echo implements high performance, minimalist Go web framework.
@ -60,17 +63,16 @@ import (
"golang.org/x/net/http2/h2c"
)
type (
// Echo is the top-level framework instance.
//
// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
// fields from handlers/middlewares and changing field values at the same time leads to data-races.
// Adding new routes after the server has been started is also not safe!
Echo struct {
type Echo struct {
filesystem
common
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// listener address info (on which interface/port was listener binded) without having data races.
// listener address info (on which interface/port was listener bound) without having data races.
startupMutex sync.RWMutex
colorer *color.Color
@ -107,50 +109,49 @@ type (
}
// Route contains a handler and information for matching against requests.
Route struct {
type Route struct {
Method string `json:"method"`
Path string `json:"path"`
Name string `json:"name"`
}
// HTTPError represents an error that occurred while handling a request.
HTTPError struct {
type HTTPError struct {
Code int `json:"-"`
Message interface{} `json:"message"`
Internal error `json:"-"` // Stores the error returned by an external dependency
}
// MiddlewareFunc defines a function to process middleware.
MiddlewareFunc func(next HandlerFunc) HandlerFunc
type MiddlewareFunc func(next HandlerFunc) HandlerFunc
// HandlerFunc defines a function to serve HTTP requests.
HandlerFunc func(c Context) error
type HandlerFunc func(c Context) error
// HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(err error, c Context)
type HTTPErrorHandler func(err error, c Context)
// Validator is the interface that wraps the Validate function.
Validator interface {
type Validator interface {
Validate(i interface{}) error
}
// JSONSerializer is the interface that encodes and decodes JSON to and from interfaces.
JSONSerializer interface {
type JSONSerializer interface {
Serialize(c Context, i interface{}, indent string) error
Deserialize(c Context, i interface{}) error
}
// Renderer is the interface that wraps the Render function.
Renderer interface {
type Renderer interface {
Render(io.Writer, string, interface{}, Context) error
}
// Map defines a generic map of type `map[string]interface{}`.
Map map[string]interface{}
type Map map[string]interface{}
// Common struct for Echo & Group.
common struct{}
)
type common struct{}
// HTTP methods
// NOTE: Deprecated, please use the stdlib constants directly instead.
@ -169,7 +170,12 @@ const (
// MIME types
const (
// MIMEApplicationJSON JavaScript Object Notation (JSON) https://www.rfc-editor.org/rfc/rfc8259
MIMEApplicationJSON = "application/json"
// Deprecated: Please use MIMEApplicationJSON instead. JSON should be encoded using UTF-8 by default.
// No "charset" parameter is defined for this registration.
// Adding one really has no effect on compliant recipients.
// See RFC 8259, section 8.1. https://datatracker.ietf.org/doc/html/rfc8259#section-8.1
MIMEApplicationJSONCharsetUTF8 = MIMEApplicationJSON + "; " + charsetUTF8
MIMEApplicationJavaScript = "application/javascript"
MIMEApplicationJavaScriptCharsetUTF8 = MIMEApplicationJavaScript + "; " + charsetUTF8
@ -259,7 +265,7 @@ const (
const (
// Version of Echo
Version = "4.11.1"
Version = "4.12.0"
website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = `
@ -274,8 +280,7 @@ ____________________________________O/_______
`
)
var (
methods = [...]string{
var methods = [...]string{
http.MethodConnect,
http.MethodDelete,
http.MethodGet,
@ -288,7 +293,6 @@ var (
http.MethodTrace,
REPORT,
}
)
// Errors
var (
@ -341,13 +345,15 @@ var (
ErrInvalidListenerNetwork = errors.New("invalid listener network")
)
// Error handlers
var (
NotFoundHandler = func(c Context) error {
// NotFoundHandler is the handler that router uses in case there was no matching route found. Returns an error that results
// HTTP 404 status code.
var NotFoundHandler = func(c Context) error {
return ErrNotFound
}
MethodNotAllowedHandler = func(c Context) error {
// MethodNotAllowedHandler is the handler thar router uses in case there was no matching route found but there was
// another matching routes for that requested URL. Returns an error that results HTTP 405 Method Not Allowed status code.
var MethodNotAllowedHandler = func(c Context) error {
// See RFC 7231 section 7.4.1: An origin server MUST generate an Allow field in a 405 (Method Not Allowed)
// response and MAY do so in any other response. For disabled resources an empty Allow header may be returned
routerAllowMethods, ok := c.Get(ContextKeyHeaderAllow).(string)
@ -356,7 +362,6 @@ var (
}
return ErrMethodNotAllowed
}
)
// New creates an instance of Echo.
func New() (e *Echo) {
@ -414,7 +419,7 @@ func (e *Echo) Routers() map[string]*Router {
//
// NOTE: In case errors happens in middleware call-chain that is returning from handler (which did not return an error).
// When handler has already sent response (ala c.JSON()) and there is error in middleware that is returning from
// handler. Then the error that global error handler received will be ignored because we have already "commited" the
// handler. Then the error that global error handler received will be ignored because we have already "committed" the
// response and status code header has been sent to the client.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (

View File

@ -1,21 +1,22 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"net/http"
)
type (
// Group is a set of sub-routes for a specified route. It can be used for inner
// routes that share a common middleware or functionality that should be separate
// from the parent echo instance while still inheriting from it.
Group struct {
type Group struct {
common
host string
prefix string
middleware []MiddlewareFunc
echo *Echo
}
)
// Use implements `Echo#Use()` for sub-routes within the Group.
func (g *Group) Use(middleware ...MiddlewareFunc) {

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
@ -64,7 +67,7 @@ XFF: "x" "x, a" "x, a, b"
```
In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre".
configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructure".
In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
@ -225,15 +228,21 @@ func extractIP(req *http.Request) string {
func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
checker := newIPChecker(options)
return func(req *http.Request) string {
directIP := extractIP(req)
realIP := req.Header.Get(HeaderXRealIP)
if realIP != "" {
if realIP == "" {
return directIP
}
if checker.trust(net.ParseIP(directIP)) {
realIP = strings.TrimPrefix(realIP, "[")
realIP = strings.TrimSuffix(realIP, "]")
if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) {
if rIP := net.ParseIP(realIP); rIP != nil {
return realIP
}
}
return extractIP(req)
return directIP
}
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (

View File

@ -1,14 +1,15 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"io"
"github.com/labstack/gommon/log"
"io"
)
type (
// Logger defines the logging interface.
Logger interface {
type Logger interface {
Output() io.Writer
SetOutput(w io.Writer)
Prefix() string
@ -38,4 +39,3 @@ type (
Panicj(j log.JSON)
Panicf(format string, args ...interface{})
}
)

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -9,9 +12,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// BasicAuthConfig defines the config for BasicAuth middleware.
BasicAuthConfig struct {
type BasicAuthConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -25,21 +27,20 @@ type (
}
// BasicAuthValidator defines a function to validate BasicAuth credentials.
BasicAuthValidator func(string, string, echo.Context) (bool, error)
)
// The function should return a boolean indicating whether the credentials are valid,
// and an error if any error occurs during the validation process.
type BasicAuthValidator func(string, string, echo.Context) (bool, error)
const (
basic = "basic"
defaultRealm = "Restricted"
)
var (
// DefaultBasicAuthConfig is the default BasicAuth middleware config.
DefaultBasicAuthConfig = BasicAuthConfig{
var DefaultBasicAuthConfig = BasicAuthConfig{
Skipper: DefaultSkipper,
Realm: defaultRealm,
}
)
// BasicAuth returns an BasicAuth middleware.
//

View File

@ -1,8 +1,12 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
"bufio"
"bytes"
"errors"
"io"
"net"
"net/http"
@ -10,9 +14,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// BodyDumpConfig defines the config for BodyDump middleware.
BodyDumpConfig struct {
type BodyDumpConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -22,20 +25,17 @@ type (
}
// BodyDumpHandler receives the request and response payload.
BodyDumpHandler func(echo.Context, []byte, []byte)
type BodyDumpHandler func(echo.Context, []byte, []byte)
bodyDumpResponseWriter struct {
type bodyDumpResponseWriter struct {
io.Writer
http.ResponseWriter
}
)
var (
// DefaultBodyDumpConfig is the default BodyDump middleware config.
DefaultBodyDumpConfig = BodyDumpConfig{
var DefaultBodyDumpConfig = BodyDumpConfig{
Skipper: DefaultSkipper,
}
)
// BodyDump returns a BodyDump middleware.
//
@ -98,9 +98,16 @@ func (w *bodyDumpResponseWriter) Write(b []byte) (int, error) {
}
func (w *bodyDumpResponseWriter) Flush() {
w.ResponseWriter.(http.Flusher).Flush()
err := responseControllerFlush(w.ResponseWriter)
if err != nil && errors.Is(err, http.ErrNotSupported) {
panic(errors.New("response writer flushing is not supported"))
}
}
func (w *bodyDumpResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
return responseControllerHijack(w.ResponseWriter)
}
func (w *bodyDumpResponseWriter) Unwrap() http.ResponseWriter {
return w.ResponseWriter
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -9,9 +12,8 @@ import (
"github.com/labstack/gommon/bytes"
)
type (
// BodyLimitConfig defines the config for BodyLimit middleware.
BodyLimitConfig struct {
type BodyLimitConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -21,20 +23,16 @@ type (
limit int64
}
limitedReader struct {
type limitedReader struct {
BodyLimitConfig
reader io.ReadCloser
read int64
context echo.Context
}
)
var (
// DefaultBodyLimitConfig is the default BodyLimit middleware config.
DefaultBodyLimitConfig = BodyLimitConfig{
var DefaultBodyLimitConfig = BodyLimitConfig{
Skipper: DefaultSkipper,
}
)
// BodyLimit returns a BodyLimit middleware.
//
@ -80,7 +78,7 @@ func BodyLimitWithConfig(config BodyLimitConfig) echo.MiddlewareFunc {
// Based on content read
r := pool.Get().(*limitedReader)
r.Reset(req.Body, c)
r.Reset(req.Body)
defer pool.Put(r)
req.Body = r
@ -102,9 +100,8 @@ func (r *limitedReader) Close() error {
return r.reader.Close()
}
func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
func (r *limitedReader) Reset(reader io.ReadCloser) {
r.reader = reader
r.context = context
r.read = 0
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -13,9 +16,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// GzipConfig defines the config for Gzip middleware.
GzipConfig struct {
type GzipConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -37,7 +39,7 @@ type (
MinLength int
}
gzipResponseWriter struct {
type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
wroteHeader bool
@ -47,20 +49,17 @@ type (
buffer *bytes.Buffer
code int
}
)
const (
gzipScheme = "gzip"
)
var (
// DefaultGzipConfig is the default Gzip middleware config.
DefaultGzipConfig = GzipConfig{
var DefaultGzipConfig = GzipConfig{
Skipper: DefaultSkipper,
Level: -1,
MinLength: 0,
}
)
// Gzip returns a middleware which compresses HTTP response using gzip compression
// scheme.
@ -191,13 +190,15 @@ func (w *gzipResponseWriter) Flush() {
}
w.Writer.(*gzip.Writer).Flush()
if flusher, ok := w.ResponseWriter.(http.Flusher); ok {
flusher.Flush()
_ = responseControllerFlush(w.ResponseWriter)
}
func (w *gzipResponseWriter) Unwrap() http.ResponseWriter {
return w.ResponseWriter
}
func (w *gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return w.ResponseWriter.(http.Hijacker).Hijack()
return responseControllerHijack(w.ResponseWriter)
}
func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -13,7 +16,7 @@ type ContextTimeoutConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// ErrorHandler is a function when error aries in middeware execution.
// ErrorHandler is a function when error aries in middleware execution.
ErrorHandler func(err error, c echo.Context) error
// Timeout configures a timeout for the middleware, defaults to 0 for no timeout

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -9,9 +12,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// CORSConfig defines the config for CORS middleware.
CORSConfig struct {
type CORSConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -39,7 +41,7 @@ type (
// See https://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html
//
// Optional.
AllowOriginFunc func(origin string) (bool, error) `yaml:"allow_origin_func"`
AllowOriginFunc func(origin string) (bool, error) `yaml:"-"`
// AllowMethods determines the value of the Access-Control-Allow-Methods
// response header. This header specified the list of methods allowed when
@ -99,22 +101,20 @@ type (
// MaxAge determines the value of the Access-Control-Max-Age response header.
// This header indicates how long (in seconds) the results of a preflight
// request can be cached.
// The header is set only if MaxAge != 0, negative value sends "0" which instructs browsers not to cache that response.
//
// Optional. Default value 0. The header is set only if MaxAge > 0.
// Optional. Default value 0 - meaning header is not sent.
//
// See also: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
MaxAge int `yaml:"max_age"`
}
)
var (
// DefaultCORSConfig is the default CORS middleware config.
DefaultCORSConfig = CORSConfig{
var DefaultCORSConfig = CORSConfig{
Skipper: DefaultSkipper,
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}
)
// CORS returns a Cross-Origin Resource Sharing (CORS) middleware.
// See also [MDN: Cross-Origin Resource Sharing (CORS)].
@ -159,7 +159,11 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
allowMethods := strings.Join(config.AllowMethods, ",")
allowHeaders := strings.Join(config.AllowHeaders, ",")
exposeHeaders := strings.Join(config.ExposeHeaders, ",")
maxAge := strconv.Itoa(config.MaxAge)
maxAge := "0"
if config.MaxAge > 0 {
maxAge = strconv.Itoa(config.MaxAge)
}
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
@ -282,7 +286,7 @@ func CORSWithConfig(config CORSConfig) echo.MiddlewareFunc {
res.Header().Set(echo.HeaderAccessControlAllowHeaders, h)
}
}
if config.MaxAge > 0 {
if config.MaxAge != 0 {
res.Header().Set(echo.HeaderAccessControlMaxAge, maxAge)
}
return c.NoContent(http.StatusNoContent)

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -6,12 +9,10 @@ import (
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/random"
)
type (
// CSRFConfig defines the config for CSRF middleware.
CSRFConfig struct {
type CSRFConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -67,15 +68,13 @@ type (
}
// CSRFErrorHandler is a function which is executed for creating custom errors.
CSRFErrorHandler func(err error, c echo.Context) error
)
type CSRFErrorHandler func(err error, c echo.Context) error
// ErrCSRFInvalid is returned when CSRF check fails
var ErrCSRFInvalid = echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
var (
// DefaultCSRFConfig is the default CSRF middleware config.
DefaultCSRFConfig = CSRFConfig{
var DefaultCSRFConfig = CSRFConfig{
Skipper: DefaultSkipper,
TokenLength: 32,
TokenLookup: "header:" + echo.HeaderXCSRFToken,
@ -84,7 +83,6 @@ var (
CookieMaxAge: 86400,
CookieSameSite: http.SameSiteDefaultMode,
}
)
// CSRF returns a Cross-Site Request Forgery (CSRF) middleware.
// See: https://en.wikipedia.org/wiki/Cross-site_request_forgery
@ -103,6 +101,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
if config.TokenLength == 0 {
config.TokenLength = DefaultCSRFConfig.TokenLength
}
if config.TokenLookup == "" {
config.TokenLookup = DefaultCSRFConfig.TokenLookup
}
@ -132,7 +131,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
token := ""
if k, err := c.Cookie(config.CookieName); err != nil {
token = random.String(config.TokenLength) // Generate token
token = randomString(config.TokenLength)
} else {
token = k.Value // Reuse token
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -9,16 +12,14 @@ import (
"github.com/labstack/echo/v4"
)
type (
// DecompressConfig defines the config for Decompress middleware.
DecompressConfig struct {
type DecompressConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// GzipDecompressPool defines an interface to provide the sync.Pool used to create/store Gzip readers
GzipDecompressPool Decompressor
}
)
// GZIPEncoding content-encoding header if set to "gzip", decompress body contents.
const GZIPEncoding string = "gzip"
@ -28,13 +29,11 @@ type Decompressor interface {
gzipDecompressPool() sync.Pool
}
var (
// DefaultDecompressConfig defines the config for decompress middleware
DefaultDecompressConfig = DecompressConfig{
var DefaultDecompressConfig = DecompressConfig{
Skipper: DefaultSkipper,
GzipDecompressPool: &DefaultGzipDecompressPool{},
}
)
// DefaultGzipDecompressPool is the default implementation of Decompressor interface
type DefaultGzipDecompressPool struct {

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build go1.15
// +build go1.15
@ -12,9 +15,8 @@ import (
"reflect"
)
type (
// JWTConfig defines the config for JWT middleware.
JWTConfig struct {
type JWTConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -112,29 +114,27 @@ type (
}
// JWTSuccessHandler defines a function which is executed for a valid token.
JWTSuccessHandler func(c echo.Context)
type JWTSuccessHandler func(c echo.Context)
// JWTErrorHandler defines a function which is executed for an invalid token.
JWTErrorHandler func(err error) error
type JWTErrorHandler func(err error) error
// JWTErrorHandlerWithContext is almost identical to JWTErrorHandler, but it's passed the current context.
JWTErrorHandlerWithContext func(err error, c echo.Context) error
)
type JWTErrorHandlerWithContext func(err error, c echo.Context) error
// Algorithms
const (
AlgorithmHS256 = "HS256"
)
// Errors
var (
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
)
// ErrJWTMissing is error that is returned when no JWToken was extracted from the request.
var ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
// ErrJWTInvalid is error that is returned when middleware could not parse JWT correctly.
var ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
var (
// DefaultJWTConfig is the default JWT auth middleware config.
DefaultJWTConfig = JWTConfig{
var DefaultJWTConfig = JWTConfig{
Skipper: DefaultSkipper,
SigningMethod: AlgorithmHS256,
ContextKey: "user",
@ -144,7 +144,6 @@ var (
Claims: jwt.MapClaims{},
KeyFunc: nil,
}
)
// JWT returns a JSON Web Token (JWT) auth middleware.
//

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -6,9 +9,8 @@ import (
"net/http"
)
type (
// KeyAuthConfig defines the config for KeyAuth middleware.
KeyAuthConfig struct {
type KeyAuthConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -49,26 +51,23 @@ type (
}
// KeyAuthValidator defines a function to validate KeyAuth credentials.
KeyAuthValidator func(auth string, c echo.Context) (bool, error)
type KeyAuthValidator func(auth string, c echo.Context) (bool, error)
// KeyAuthErrorHandler defines a function which is executed for an invalid key.
KeyAuthErrorHandler func(err error, c echo.Context) error
)
var (
// DefaultKeyAuthConfig is the default KeyAuth middleware config.
DefaultKeyAuthConfig = KeyAuthConfig{
Skipper: DefaultSkipper,
KeyLookup: "header:" + echo.HeaderAuthorization,
AuthScheme: "Bearer",
}
)
type KeyAuthErrorHandler func(err error, c echo.Context) error
// ErrKeyAuthMissing is error type when KeyAuth middleware is unable to extract value from lookups
type ErrKeyAuthMissing struct {
Err error
}
// DefaultKeyAuthConfig is the default KeyAuth middleware config.
var DefaultKeyAuthConfig = KeyAuthConfig{
Skipper: DefaultSkipper,
KeyLookup: "header:" + echo.HeaderAuthorization,
AuthScheme: "Bearer",
}
// Error returns errors text
func (e *ErrKeyAuthMissing) Error() string {
return e.Err.Error()

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -14,9 +17,8 @@ import (
"github.com/valyala/fasttemplate"
)
type (
// LoggerConfig defines the config for Logger middleware.
LoggerConfig struct {
type LoggerConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -71,11 +73,9 @@ type (
colorer *color.Color
pool *sync.Pool
}
)
var (
// DefaultLoggerConfig is the default Logger middleware config.
DefaultLoggerConfig = LoggerConfig{
var DefaultLoggerConfig = LoggerConfig{
Skipper: DefaultSkipper,
Format: `{"time":"${time_rfc3339_nano}","id":"${id}","remote_ip":"${remote_ip}",` +
`"host":"${host}","method":"${method}","uri":"${uri}","user_agent":"${user_agent}",` +
@ -84,7 +84,6 @@ var (
CustomTimeFormat: "2006-01-02 15:04:05.00000",
colorer: color.New(),
}
)
// Logger returns a middleware that logs HTTP requests.
func Logger() echo.MiddlewareFunc {

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -6,9 +9,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// MethodOverrideConfig defines the config for MethodOverride middleware.
MethodOverrideConfig struct {
type MethodOverrideConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -18,16 +20,13 @@ type (
}
// MethodOverrideGetter is a function that gets overridden method from the request
MethodOverrideGetter func(echo.Context) string
)
type MethodOverrideGetter func(echo.Context) string
var (
// DefaultMethodOverrideConfig is the default MethodOverride middleware config.
DefaultMethodOverrideConfig = MethodOverrideConfig{
var DefaultMethodOverrideConfig = MethodOverrideConfig{
Skipper: DefaultSkipper,
Getter: MethodFromHeader(echo.HeaderXHTTPMethodOverride),
}
)
// MethodOverride returns a MethodOverride middleware.
// MethodOverride middleware checks for the overridden method from the request and

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -9,14 +12,12 @@ import (
"github.com/labstack/echo/v4"
)
type (
// Skipper defines a function to skip middleware. Returning true skips processing
// the middleware.
Skipper func(c echo.Context) bool
type Skipper func(c echo.Context) bool
// BeforeFunc defines a function which is executed just before the middleware.
BeforeFunc func(c echo.Context)
)
type BeforeFunc func(c echo.Context)
func captureTokens(pattern *regexp.Regexp, input string) *strings.Replacer {
groups := pattern.FindAllStringSubmatch(input, -1)
@ -53,7 +54,7 @@ func rewriteURL(rewriteRegex map[*regexp.Regexp]string, req *http.Request) error
return nil
}
// Depending how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path.
// Depending on how HTTP request is sent RequestURI could contain Scheme://Host/path or be just /path.
// We only want to use path part for rewriting and therefore trim prefix if it exists
rawURI := req.RequestURI
if rawURI != "" && rawURI[0] != '/' {

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -19,9 +22,8 @@ import (
// TODO: Handle TLS proxy
type (
// ProxyConfig defines the config for Proxy middleware.
ProxyConfig struct {
type ProxyConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -85,14 +87,14 @@ type (
}
// ProxyTarget defines the upstream target.
ProxyTarget struct {
type ProxyTarget struct {
Name string
URL *url.URL
Meta echo.Map
}
// ProxyBalancer defines an interface to implement a load balancing technique.
ProxyBalancer interface {
type ProxyBalancer interface {
AddTarget(*ProxyTarget) bool
RemoveTarget(string) bool
Next(echo.Context) *ProxyTarget
@ -100,36 +102,33 @@ type (
// TargetProvider defines an interface that gives the opportunity for balancer
// to return custom errors when selecting target.
TargetProvider interface {
type TargetProvider interface {
NextTarget(echo.Context) (*ProxyTarget, error)
}
commonBalancer struct {
type commonBalancer struct {
targets []*ProxyTarget
mutex sync.Mutex
}
// RandomBalancer implements a random load balancing technique.
randomBalancer struct {
type randomBalancer struct {
commonBalancer
random *rand.Rand
}
// RoundRobinBalancer implements a round-robin load balancing technique.
roundRobinBalancer struct {
type roundRobinBalancer struct {
commonBalancer
// tracking the index on `targets` slice for the next `*ProxyTarget` to be used
i int
}
)
var (
// DefaultProxyConfig is the default Proxy middleware config.
DefaultProxyConfig = ProxyConfig{
var DefaultProxyConfig = ProxyConfig{
Skipper: DefaultSkipper,
ContextKey: "target",
}
)
func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -359,12 +358,15 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
c.Set("_error", nil)
}
// This is needed for ProxyConfig.ModifyResponse and/or ProxyConfig.Transport to be able to process the Request
// that Balancer may have replaced with c.SetRequest.
req = c.Request()
// Proxy
switch {
case c.IsWebSocket():
proxyRaw(tgt, c).ServeHTTP(res, req)
case req.Header.Get(echo.HeaderAccept) == "text/event-stream":
default:
default: // even SSE requests
proxyHTTP(tgt, c, config).ServeHTTP(res, req)
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -9,17 +12,14 @@ import (
"golang.org/x/time/rate"
)
type (
// RateLimiterStore is the interface to be implemented by custom stores.
RateLimiterStore interface {
type RateLimiterStore interface {
// Stores for the rate limiter have to implement the Allow method
Allow(identifier string) (bool, error)
}
)
type (
// RateLimiterConfig defines the configuration for the rate limiter
RateLimiterConfig struct {
type RateLimiterConfig struct {
Skipper Skipper
BeforeFunc BeforeFunc
// IdentifierExtractor uses echo.Context to extract the identifier for a visitor
@ -31,17 +31,15 @@ type (
// DenyHandler provides a handler to be called when RateLimiter denies access
DenyHandler func(context echo.Context, identifier string, err error) error
}
// Extractor is used to extract data from echo.Context
Extractor func(context echo.Context) (string, error)
)
// errors
var (
// Extractor is used to extract data from echo.Context
type Extractor func(context echo.Context) (string, error)
// ErrRateLimitExceeded denotes an error raised when rate limit is exceeded
ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
var ErrRateLimitExceeded = echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded")
// ErrExtractorError denotes an error raised when extractor function is unsuccessful
ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier")
)
var ErrExtractorError = echo.NewHTTPError(http.StatusForbidden, "error while extracting identifier")
// DefaultRateLimiterConfig defines default values for RateLimiterConfig
var DefaultRateLimiterConfig = RateLimiterConfig{
@ -150,9 +148,8 @@ func RateLimiterWithConfig(config RateLimiterConfig) echo.MiddlewareFunc {
}
}
type (
// RateLimiterMemoryStore is the built-in store implementation for RateLimiter
RateLimiterMemoryStore struct {
type RateLimiterMemoryStore struct {
visitors map[string]*Visitor
mutex sync.Mutex
rate rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
@ -163,12 +160,12 @@ type (
timeNow func() time.Time
}
// Visitor signifies a unique user's limiter details
Visitor struct {
type Visitor struct {
*rate.Limiter
lastSeen time.Time
}
)
/*
NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -9,12 +12,11 @@ import (
"github.com/labstack/gommon/log"
)
type (
// LogErrorFunc defines a function for custom logging in the middleware.
LogErrorFunc func(c echo.Context, err error, stack []byte) error
type LogErrorFunc func(c echo.Context, err error, stack []byte) error
// RecoverConfig defines the config for Recover middleware.
RecoverConfig struct {
type RecoverConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -45,11 +47,9 @@ type (
// Optional. Default value false.
DisableErrorHandler bool `yaml:"disable_error_handler"`
}
)
var (
// DefaultRecoverConfig is the default Recover middleware config.
DefaultRecoverConfig = RecoverConfig{
var DefaultRecoverConfig = RecoverConfig{
Skipper: DefaultSkipper,
StackSize: 4 << 10, // 4 KB
DisableStackAll: false,
@ -58,7 +58,6 @@ var (
LogErrorFunc: nil,
DisableErrorHandler: false,
}
)
// Recover returns a middleware which recovers from panics anywhere in the chain
// and handles the control to the centralized HTTPErrorHandler.

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (

View File

@ -1,18 +1,19 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/random"
)
type (
// RequestIDConfig defines the config for RequestID middleware.
RequestIDConfig struct {
type RequestIDConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
// Generator defines a function to generate an ID.
// Optional. Default value random.String(32).
// Optional. Defaults to generator for random string of length 32.
Generator func() string
// RequestIDHandler defines a function which is executed for a request id.
@ -21,16 +22,13 @@ type (
// TargetHeader defines what header to look for to populate the id
TargetHeader string
}
)
var (
// DefaultRequestIDConfig is the default RequestID middleware config.
DefaultRequestIDConfig = RequestIDConfig{
var DefaultRequestIDConfig = RequestIDConfig{
Skipper: DefaultSkipper,
Generator: generator,
TargetHeader: echo.HeaderXRequestID,
}
)
// RequestID returns a X-Request-ID middleware.
func RequestID() echo.MiddlewareFunc {
@ -73,5 +71,5 @@ func RequestIDWithConfig(config RequestIDConfig) echo.MiddlewareFunc {
}
func generator() string {
return random.String(32)
return randomString(32)
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -8,6 +11,30 @@ import (
"github.com/labstack/echo/v4"
)
// Example for `slog` https://pkg.go.dev/log/slog
// logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogStatus: true,
// LogURI: true,
// LogError: true,
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// if v.Error == nil {
// logger.LogAttrs(context.Background(), slog.LevelInfo, "REQUEST",
// slog.String("uri", v.URI),
// slog.Int("status", v.Status),
// )
// } else {
// logger.LogAttrs(context.Background(), slog.LevelError, "REQUEST_ERROR",
// slog.String("uri", v.URI),
// slog.Int("status", v.Status),
// slog.String("err", v.Error.Error()),
// )
// }
// return nil
// },
// }))
//
// Example for `fmt.Printf`
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogStatus: true,

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build !go1.20
package middleware
import (
"bufio"
"fmt"
"net"
"net/http"
)
// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
func responseControllerFlush(rw http.ResponseWriter) error {
for {
switch t := rw.(type) {
case interface{ FlushError() error }:
return t.FlushError()
case http.Flusher:
t.Flush()
return nil
case interface{ Unwrap() http.ResponseWriter }:
rw = t.Unwrap()
default:
return fmt.Errorf("%w", http.ErrNotSupported)
}
}
}
// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
for {
switch t := rw.(type) {
case http.Hijacker:
return t.Hijack()
case interface{ Unwrap() http.ResponseWriter }:
rw = t.Unwrap()
default:
return nil, nil, fmt.Errorf("%w", http.ErrNotSupported)
}
}
}

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build go1.20
package middleware
import (
"bufio"
"net"
"net/http"
)
func responseControllerFlush(rw http.ResponseWriter) error {
return http.NewResponseController(rw).Flush()
}
func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
return http.NewResponseController(rw).Hijack()
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -6,9 +9,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// RewriteConfig defines the config for Rewrite middleware.
RewriteConfig struct {
type RewriteConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -27,16 +29,13 @@ type (
// Example:
// "^/old/[0.9]+/": "/new",
// "^/api/.+?/(.*)": "/v2/$1",
RegexRules map[*regexp.Regexp]string `yaml:"regex_rules"`
RegexRules map[*regexp.Regexp]string `yaml:"-"`
}
)
var (
// DefaultRewriteConfig is the default Rewrite middleware config.
DefaultRewriteConfig = RewriteConfig{
var DefaultRewriteConfig = RewriteConfig{
Skipper: DefaultSkipper,
}
)
// Rewrite returns a Rewrite middleware.
//

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -6,9 +9,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// SecureConfig defines the config for Secure middleware.
SecureConfig struct {
type SecureConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -72,18 +74,15 @@ type (
// Optional. Default value "".
ReferrerPolicy string `yaml:"referrer_policy"`
}
)
var (
// DefaultSecureConfig is the default Secure middleware config.
DefaultSecureConfig = SecureConfig{
var DefaultSecureConfig = SecureConfig{
Skipper: DefaultSkipper,
XSSProtection: "1; mode=block",
ContentTypeNosniff: "nosniff",
XFrameOptions: "SAMEORIGIN",
HSTSPreloadEnabled: false,
}
)
// Secure returns a Secure middleware.
// Secure middleware provides protection against cross-site scripting (XSS) attack,

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -6,9 +9,8 @@ import (
"github.com/labstack/echo/v4"
)
type (
// TrailingSlashConfig defines the config for TrailingSlash middleware.
TrailingSlashConfig struct {
type TrailingSlashConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -16,14 +18,11 @@ type (
// Optional, but when provided the request is redirected using this code.
RedirectCode int `yaml:"redirect_code"`
}
)
var (
// DefaultTrailingSlashConfig is the default TrailingSlash middleware config.
DefaultTrailingSlashConfig = TrailingSlashConfig{
var DefaultTrailingSlashConfig = TrailingSlashConfig{
Skipper: DefaultSkipper,
}
)
// AddTrailingSlash returns a root level (before router) middleware which adds a
// trailing slash to the request `URL#Path`.

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -14,9 +17,8 @@ import (
"github.com/labstack/gommon/bytes"
)
type (
// StaticConfig defines the config for Static middleware.
StaticConfig struct {
type StaticConfig struct {
// Skipper defines a function to skip middleware.
Skipper Skipper
@ -47,7 +49,6 @@ type (
// Optional. Defaults to http.Dir(config.Root)
Filesystem http.FileSystem `yaml:"-"`
}
)
const html = `
<!DOCTYPE html>
@ -121,13 +122,11 @@ const html = `
</html>
`
var (
// DefaultStaticConfig is the default Static middleware config.
DefaultStaticConfig = StaticConfig{
var DefaultStaticConfig = StaticConfig{
Skipper: DefaultSkipper,
Index: "index.html",
}
)
// Static returns a Static middleware to serves static content from the provided
// root directory.

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build !windows
package middleware

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
@ -77,14 +80,12 @@ type TimeoutConfig struct {
Timeout time.Duration
}
var (
// DefaultTimeoutConfig is the default Timeout middleware config.
DefaultTimeoutConfig = TimeoutConfig{
var DefaultTimeoutConfig = TimeoutConfig{
Skipper: DefaultSkipper,
Timeout: 0,
ErrorMessage: "",
}
)
// Timeout returns a middleware which returns error (503 Service Unavailable error) to client immediately when handler
// call runs for longer than its time limit. NB: timeout does not stop handler execution.

View File

@ -1,7 +1,14 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package middleware
import (
"bufio"
"crypto/rand"
"io"
"strings"
"sync"
)
func matchScheme(domain, pattern string) bool {
@ -52,3 +59,45 @@ func matchSubdomain(domain, pattern string) bool {
}
return false
}
// https://tip.golang.org/doc/go1.19#:~:text=Read%20no%20longer%20buffers%20random%20data%20obtained%20from%20the%20operating%20system%20between%20calls
var randomReaderPool = sync.Pool{New: func() interface{} {
return bufio.NewReader(rand.Reader)
}}
const randomStringCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
const randomStringCharsetLen = 52 // len(randomStringCharset)
const randomStringMaxByte = 255 - (256 % randomStringCharsetLen)
func randomString(length uint8) string {
reader := randomReaderPool.Get().(*bufio.Reader)
defer randomReaderPool.Put(reader)
b := make([]byte, length)
r := make([]byte, length+(length/4)) // perf: avoid read from rand.Reader many times
var i uint8 = 0
// security note:
// we can't just simply do b[i]=randomStringCharset[rb%len(randomStringCharset)],
// len(len(randomStringCharset)) is 52, and rb is [0, 255], 256 = 52 * 4 + 48.
// make the first 48 characters more possibly to be generated then others.
// So we have to skip bytes when rb > randomStringMaxByte
for {
_, err := io.ReadFull(reader, r)
if err != nil {
panic("unexpected error happened when reading from bufio.NewReader(crypto/rand.Reader)")
}
for _, rb := range r {
if rb > randomStringMaxByte {
// Skip this number to avoid bias.
continue
}
b[i] = randomStringCharset[rb%randomStringCharsetLen]
i++
if i == length {
return string(b)
}
}
}
}

View File

@ -1,16 +1,19 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
"bufio"
"errors"
"net"
"net/http"
)
type (
// Response wraps an http.ResponseWriter and implements its interface to be used
// by an HTTP handler to construct an HTTP response.
// See: https://golang.org/pkg/net/http/#ResponseWriter
Response struct {
type Response struct {
echo *Echo
beforeFuncs []func()
afterFuncs []func()
@ -19,7 +22,6 @@ type (
Size int64
Committed bool
}
)
// NewResponse creates a new instance of Response.
func NewResponse(w http.ResponseWriter, e *Echo) (r *Response) {
@ -84,14 +86,17 @@ func (r *Response) Write(b []byte) (n int, err error) {
// buffered data to the client.
// See [http.Flusher](https://golang.org/pkg/net/http/#Flusher)
func (r *Response) Flush() {
r.Writer.(http.Flusher).Flush()
err := responseControllerFlush(r.Writer)
if err != nil && errors.Is(err, http.ErrNotSupported) {
panic(errors.New("response writer flushing is not supported"))
}
}
// Hijack implements the http.Hijacker interface to allow an HTTP handler to
// take over the connection.
// See [http.Hijacker](https://golang.org/pkg/net/http/#Hijacker)
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return r.Writer.(http.Hijacker).Hijack()
return responseControllerHijack(r.Writer)
}
// Unwrap returns the original http.ResponseWriter.

View File

@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build !go1.20
package echo
import (
"bufio"
"fmt"
"net"
"net/http"
)
// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
func responseControllerFlush(rw http.ResponseWriter) error {
for {
switch t := rw.(type) {
case interface{ FlushError() error }:
return t.FlushError()
case http.Flusher:
t.Flush()
return nil
case interface{ Unwrap() http.ResponseWriter }:
rw = t.Unwrap()
default:
return fmt.Errorf("%w", http.ErrNotSupported)
}
}
}
// TODO: remove when Go 1.23 is released and we do not support 1.19 anymore
func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
for {
switch t := rw.(type) {
case http.Hijacker:
return t.Hijack()
case interface{ Unwrap() http.ResponseWriter }:
rw = t.Unwrap()
default:
return nil, nil, fmt.Errorf("%w", http.ErrNotSupported)
}
}
}

View File

@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
//go:build go1.20
package echo
import (
"bufio"
"net"
"net/http"
)
func responseControllerFlush(rw http.ResponseWriter) error {
return http.NewResponseController(rw).Flush()
}
func responseControllerHijack(rw http.ResponseWriter) (net.Conn, *bufio.ReadWriter, error) {
return http.NewResponseController(rw).Hijack()
}

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: © 2015 LabStack LLC and Echo contributors
package echo
import (
@ -6,15 +9,15 @@ import (
"net/http"
)
type (
// Router is the registry of all registered routes for an `Echo` instance for
// request matching and URL path parameter parsing.
Router struct {
type Router struct {
tree *node
routes map[string]*Route
echo *Echo
}
node struct {
type node struct {
kind kind
label byte
prefix string
@ -33,14 +36,17 @@ type (
// notFoundHandler is handler registered with RouteNotFound method and is executed for 404 cases
notFoundHandler *routeMethod
}
kind uint8
children []*node
routeMethod struct {
type kind uint8
type children []*node
type routeMethod struct {
ppath string
pnames []string
handler HandlerFunc
}
routeMethods struct {
type routeMethods struct {
connect *routeMethod
delete *routeMethod
get *routeMethod
@ -55,7 +61,6 @@ type (
anyOther map[string]*routeMethod
allowHeader string
}
)
const (
staticKind kind = iota
@ -180,8 +185,18 @@ func (r *Router) Reverse(name string, params ...interface{}) string {
return uri.String()
}
func normalizePathSlash(path string) string {
if path == "" {
path = "/"
} else if path[0] != '/' {
path = "/" + path
}
return path
}
func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
r.Add(method, path, h)
path = normalizePathSlash(path)
r.insert(method, path, h)
route := &Route{
Method: method,
@ -194,13 +209,11 @@ func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
// Add registers a new route for method and path with matching handler.
func (r *Router) Add(method, path string, h HandlerFunc) {
// Validate path
if path == "" {
path = "/"
}
if path[0] != '/' {
path = "/" + path
r.insert(method, normalizePathSlash(path), h)
}
func (r *Router) insert(method, path string, h HandlerFunc) {
path = normalizePathSlash(path)
pnames := []string{} // Param names
ppath := path // Pristine path
@ -219,7 +232,7 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
}
j := i + 1
r.insert(method, path[:i], staticKind, routeMethod{})
r.insertNode(method, path[:i], staticKind, routeMethod{})
for ; i < lcpIndex && path[i] != '/'; i++ {
}
@ -229,21 +242,21 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
if i == lcpIndex {
// path node is last fragment of route path. ie. `/users/:id`
r.insert(method, path[:i], paramKind, routeMethod{ppath, pnames, h})
r.insertNode(method, path[:i], paramKind, routeMethod{ppath, pnames, h})
} else {
r.insert(method, path[:i], paramKind, routeMethod{})
r.insertNode(method, path[:i], paramKind, routeMethod{})
}
} else if path[i] == '*' {
r.insert(method, path[:i], staticKind, routeMethod{})
r.insertNode(method, path[:i], staticKind, routeMethod{})
pnames = append(pnames, "*")
r.insert(method, path[:i+1], anyKind, routeMethod{ppath, pnames, h})
r.insertNode(method, path[:i+1], anyKind, routeMethod{ppath, pnames, h})
}
}
r.insert(method, path, staticKind, routeMethod{ppath, pnames, h})
r.insertNode(method, path, staticKind, routeMethod{ppath, pnames, h})
}
func (r *Router) insert(method, path string, t kind, rm routeMethod) {
func (r *Router) insertNode(method, path string, t kind, rm routeMethod) {
// Adjust max param
paramLen := len(rm.pnames)
if *r.echo.maxParam < paramLen {

View File

@ -1,48 +0,0 @@
package random
import (
"math/rand"
"strings"
"time"
)
type (
Random struct {
}
)
// Charsets
const (
Uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Lowercase = "abcdefghijklmnopqrstuvwxyz"
Alphabetic = Uppercase + Lowercase
Numeric = "0123456789"
Alphanumeric = Alphabetic + Numeric
Symbols = "`" + `~!@#$%^&*()-_+={}[]|\;:"<>,./?`
Hex = Numeric + "abcdef"
)
var (
global = New()
)
func New() *Random {
rand.Seed(time.Now().UnixNano())
return new(Random)
}
func (r *Random) String(length uint8, charsets ...string) string {
charset := strings.Join(charsets, "")
if charset == "" {
charset = Alphanumeric
}
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Int63()%int64(len(charset))]
}
return string(b)
}
func String(length uint8, charsets ...string) string {
return global.String(length, charsets...)
}

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2012-2023 Li Kexian
Copyright 2012-2024 Li Kexian
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -200,6 +200,6 @@
See the License for the specific language governing permissions and
limitations under the License.
APPENDIX: Copyright 2012-2023 Li Kexian
APPENDIX: Copyright 2012-2024 Li Kexian
https://www.likexian.com/

View File

@ -85,7 +85,7 @@ b := assert.If(a == 1, true, false)
## License
Copyright 2012-2023 [Li Kexian](https://www.likexian.com/)
Copyright 2012-2024 [Li Kexian](https://www.likexian.com/)
Licensed under the Apache License 2.0

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 Li Kexian
* Copyright 2012-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 Li Kexian
* Copyright 2012-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -43,7 +43,7 @@ if err != nil {
## License
Copyright 2012-2023 [Li Kexian](https://www.likexian.com/)
Copyright 2012-2024 [Li Kexian](https://www.likexian.com/)
Licensed under the Apache License 2.0

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 Li Kexian
* Copyright 2012-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -34,7 +34,7 @@ fmt.Println("new array:", array)
## License
Copyright 2012-2023 [Li Kexian](https://www.likexian.com/)
Copyright 2012-2024 [Li Kexian](https://www.likexian.com/)
Licensed under the Apache License 2.0

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 Li Kexian
* Copyright 2012-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2014-2023 Li Kexian
Copyright 2014-2024 Li Kexian
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -200,6 +200,6 @@
See the License for the specific language governing permissions and
limitations under the License.
APPENDIX: Copyright 2014-2023 Li Kexian
APPENDIX: Copyright 2014-2024 Li Kexian
https://www.likexian.com/

View File

@ -71,7 +71,7 @@ Please refer to [whois](https://github.com/likexian/whois)
## License
Copyright 2014-2023 [Li Kexian](https://www.likexian.com/)
Copyright 2014-2024 [Li Kexian](https://www.likexian.com/)
Licensed under the Apache License 2.0

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 Li Kexian
* Copyright 2014-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 Li Kexian
* Copyright 2014-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -153,6 +153,8 @@ func Parse(text string) (whoisInfo WhoisInfo, err error) { //nolint:cyclop
if !strings.Contains(name, " ") {
if name == "registrar" {
name += " name"
} else if domain.Extension == "dk" {
name = "registrant " + name
} else {
name += " organization"
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 Li Kexian
* Copyright 2014-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -91,6 +91,8 @@ func Prepare(text, ext string) (string, bool) { //nolint:cyclop
return prepareBY(text), true
case "ua":
return prepareUA(text), true
case "at":
return prepareAT(text), true
default:
return text, false
}
@ -685,7 +687,7 @@ func prepareRU(text string) string {
return result
}
var prepareJPreplacerRx = regexp.MustCompile(`\n\[(.+?)\][\ ]*(.+?)?`)
var prepareJPreplacerRx = regexp.MustCompile(`\n(?:\w+\.\s)?\[(.+?)\][\ ]*(.+?)?`)
// prepareJP do prepare the .jp domain
func prepareJP(text string) string {
@ -712,6 +714,7 @@ func prepareJP(text string) string {
if strings.ToLower(token) == "registrant" {
v = fmt.Sprintf("registrant name: %s", vs[1])
}
v = prepareSecondLevelJP(v, token, vs[1])
} else {
if token == addressToken {
result += ", " + v
@ -724,6 +727,29 @@ func prepareJP(text string) string {
return result
}
// prepareJP prepares specific mappings for second level .jp domains
// examples include:
// - co.jp
// - ac.jp
// - go.jp
// - or.jp
// - ad.jp
// - ne.jp
// - gr.jp
// - ed.jp
func prepareSecondLevelJP(original string, token string, value string) string {
if strings.ToLower(token) == "administrative contact" {
return fmt.Sprintf("Administrative Contact ID: %s", strings.TrimSpace(value))
}
if strings.ToLower(token) == "technical contact" {
return fmt.Sprintf("Technical Contact ID: %s", strings.TrimSpace(value))
}
if strings.ToLower(token) == "organization" || strings.ToLower(token) == "network service name" {
return fmt.Sprintf("Registrant Organization: %s", strings.TrimSpace(value))
}
return original
}
// prepareUK do prepare the .uk domain
func prepareUK(text string) string {
tokens := map[string]string{
@ -1335,3 +1361,78 @@ func prepareUA(text string) string {
return result
}
// prepareAT prepares the .at domain
func prepareAT(text string) string {
result := ""
registrantID := ""
techID := ""
tokens := map[string]string{
"street address": "address",
"postal code": "address",
"city": "address",
"country": "address",
"e-mail": "email",
"nic-hdl": "id",
"personname": "name",
}
formatLine := func(line, token string) string {
before, after, _ := strings.Cut(line, ":")
key := strings.TrimSpace(before)
if t, ok := tokens[key]; ok {
key = t
}
val := strings.TrimSpace(after)
return fmt.Sprintf("%s %s: %s", token, key, val)
}
for _, v := range strings.Split(text, "\n\n") {
v = strings.TrimSpace(v)
if strings.HasPrefix(v, "%") {
continue
}
if strings.Contains(v, ":") {
b := strings.Split(v, "\n")
if strings.HasPrefix(b[0], "domain") {
for _, l := range b {
w := ""
if before, after, ok := strings.Cut(l, ":"); ok {
key := strings.TrimSpace(before)
val := strings.TrimSpace(after)
switch key {
case "domain":
w = fmt.Sprintf("%s: %s", "domain name", val)
case "registrant":
registrantID = val
case "tech-c":
techID = val
case "changed":
w = fmt.Sprintf("%s: %s", "updated_date", val)
case "nserver":
w = fmt.Sprintf("%s: %s", "name_servers", val)
default:
w = fmt.Sprintf("domain %s: %s", key, val)
}
if w != "" {
result += w + "\n"
}
}
}
} else if strings.HasPrefix(b[0], "personname") {
token := ""
if strings.Contains(v, registrantID) {
token = "registrant"
} else if strings.Contains(v, techID) {
token = "technical contact"
}
for _, l := range strings.Split(v, "\n") {
result += formatLine(l, token) + "\n"
}
}
}
}
return result
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 Li Kexian
* Copyright 2014-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -153,6 +153,8 @@ var (
"registrant zipcode": "registrant_postal_code",
"registrant zip code": "registrant_postal_code",
"registrant postalcode": "registrant_postal_code",
"registrant postal code": "registrant_postal_code",
"registrant contact postalcode": "registrant_postal_code",
"registrant contact postal code": "registrant_postal_code",
"registrant country": "registrant_country",
"registrant country economy": "registrant_country",

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 Li Kexian
* Copyright 2014-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014-2023 Li Kexian
* Copyright 2014-2024 Li Kexian
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -118,16 +118,20 @@ func keys(m map[string]string) []string {
// parseDateString attempts to parse a given date using a collection of common
// format strings. Date formats containing time components are tried first
// before attempts are made using date-only formats.
func parseDateString(dateString string) (time.Time, error) {
formats := [...]string{
func parseDateString(datetime string) (time.Time, error) {
datetime = strings.Trim(datetime, ".")
datetime = strings.ReplaceAll(datetime, ". ", "-")
formats := [...]string{
// Date & time formats
"2006-01-02 15:04:05",
"2006.01.02 15:04:05",
"02/01/2006 15:04:05",
"02.01.2006 15:04:05",
"02.1.2006 15:04:05",
"2.1.2006 15:04:05",
"02-Jan-2006 15:04:05",
"20060102 15:04:05",
time.ANSIC,
time.Stamp,
time.StampMilli,
@ -151,24 +155,25 @@ func parseDateString(dateString string) (time.Time, error) {
// Date only formats
"2006-01-02",
"2006. 01. 02.",
"02-Jan-2006",
"02.01.2006",
"02-01-2006",
"January _2 2006",
"Mon Jan _2 2006",
"02/01/2006",
"01/02/2006",
"2006/01/02",
"2006-Jan-02",
"2006-Jan-02.",
"before Jan-2006",
}
for _, format := range formats {
result, err := time.Parse(format, dateString)
result, err := time.Parse(format, datetime)
if err != nil {
continue
}
return result, nil
}
return time.Now(), fmt.Errorf("could not parse %s as a date", dateString)
return time.Now(), fmt.Errorf("could not parse %s as a date", datetime)
}

View File

@ -55,7 +55,7 @@ type Client struct {
// Version returns package version
func Version() string {
return "1.15.0"
return "1.15.2"
}
// Author returns package author

View File

@ -1,6 +1,7 @@
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine
//go:build (darwin || freebsd || openbsd || netbsd || dragonfly || hurd) && !appengine && !tinygo
// +build darwin freebsd openbsd netbsd dragonfly hurd
// +build !appengine
// +build !tinygo
package isatty

View File

@ -1,5 +1,6 @@
//go:build appengine || js || nacl || wasm
// +build appengine js nacl wasm
//go:build (appengine || js || nacl || tinygo || wasm) && !windows
// +build appengine js nacl tinygo wasm
// +build !windows
package isatty

View File

@ -1,6 +1,7 @@
//go:build (linux || aix || zos) && !appengine
//go:build (linux || aix || zos) && !appengine && !tinygo
// +build linux aix zos
// +build !appengine
// +build !tinygo
package isatty

View File

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build go1.12
// +build go1.12
package acme

View File

@ -20,42 +20,45 @@ import (
// TODO: Benchmark to determine if the pools are necessary. The GC may have
// improved enough that we can instead allocate chunks like this:
// make([]byte, max(16<<10, expectedBytesRemaining))
var (
dataChunkSizeClasses = []int{
1 << 10,
2 << 10,
4 << 10,
8 << 10,
16 << 10,
var dataChunkPools = [...]sync.Pool{
{New: func() interface{} { return new([1 << 10]byte) }},
{New: func() interface{} { return new([2 << 10]byte) }},
{New: func() interface{} { return new([4 << 10]byte) }},
{New: func() interface{} { return new([8 << 10]byte) }},
{New: func() interface{} { return new([16 << 10]byte) }},
}
dataChunkPools = [...]sync.Pool{
{New: func() interface{} { return make([]byte, 1<<10) }},
{New: func() interface{} { return make([]byte, 2<<10) }},
{New: func() interface{} { return make([]byte, 4<<10) }},
{New: func() interface{} { return make([]byte, 8<<10) }},
{New: func() interface{} { return make([]byte, 16<<10) }},
}
)
func getDataBufferChunk(size int64) []byte {
i := 0
for ; i < len(dataChunkSizeClasses)-1; i++ {
if size <= int64(dataChunkSizeClasses[i]) {
break
switch {
case size <= 1<<10:
return dataChunkPools[0].Get().(*[1 << 10]byte)[:]
case size <= 2<<10:
return dataChunkPools[1].Get().(*[2 << 10]byte)[:]
case size <= 4<<10:
return dataChunkPools[2].Get().(*[4 << 10]byte)[:]
case size <= 8<<10:
return dataChunkPools[3].Get().(*[8 << 10]byte)[:]
default:
return dataChunkPools[4].Get().(*[16 << 10]byte)[:]
}
}
return dataChunkPools[i].Get().([]byte)
}
func putDataBufferChunk(p []byte) {
for i, n := range dataChunkSizeClasses {
if len(p) == n {
dataChunkPools[i].Put(p)
return
}
}
switch len(p) {
case 1 << 10:
dataChunkPools[0].Put((*[1 << 10]byte)(p))
case 2 << 10:
dataChunkPools[1].Put((*[2 << 10]byte)(p))
case 4 << 10:
dataChunkPools[2].Put((*[4 << 10]byte)(p))
case 8 << 10:
dataChunkPools[3].Put((*[8 << 10]byte)(p))
case 16 << 10:
dataChunkPools[4].Put((*[16 << 10]byte)(p))
default:
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
}
}
// dataBuffer is an io.ReadWriter backed by a list of data chunks.
// Each dataBuffer is used to read DATA frames on a single stream.

View File

@ -1510,14 +1510,13 @@ func (mh *MetaHeadersFrame) checkPseudos() error {
}
func (fr *Framer) maxHeaderStringLen() int {
v := fr.maxHeaderListSize()
if uint32(int(v)) == v {
return int(v)
}
// They had a crazy big number for MaxHeaderBytes anyway,
// so give them unlimited header lengths:
v := int(fr.maxHeaderListSize())
if v < 0 {
// If maxHeaderListSize overflows an int, use no limit (0).
return 0
}
return v
}
// readMetaFrame returns 0 or more CONTINUATION frames from fr and
// merge them into the provided hf and returns a MetaHeadersFrame
@ -1565,6 +1564,7 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
if size > remainSize {
hdec.SetEmitEnabled(false)
mh.Truncated = true
remainSize = 0
return
}
remainSize -= size
@ -1577,6 +1577,36 @@ func (fr *Framer) readMetaFrame(hf *HeadersFrame) (*MetaHeadersFrame, error) {
var hc headersOrContinuation = hf
for {
frag := hc.HeaderBlockFragment()
// Avoid parsing large amounts of headers that we will then discard.
// If the sender exceeds the max header list size by too much,
// skip parsing the fragment and close the connection.
//
// "Too much" is either any CONTINUATION frame after we've already
// exceeded the max header list size (in which case remainSize is 0),
// or a frame whose encoded size is more than twice the remaining
// header list bytes we're willing to accept.
if int64(len(frag)) > int64(2*remainSize) {
if VerboseLogs {
log.Printf("http2: header list too large")
}
// It would be nice to send a RST_STREAM before sending the GOAWAY,
// but the structure of the server's frame writer makes this difficult.
return nil, ConnectionError(ErrCodeProtocol)
}
// Also close the connection after any CONTINUATION frame following an
// invalid header, since we stop tracking the size of the headers after
// an invalid one.
if invalid != nil {
if VerboseLogs {
log.Printf("http2: invalid header: %v", invalid)
}
// It would be nice to send a RST_STREAM before sending the GOAWAY,
// but the structure of the server's frame writer makes this difficult.
return nil, ConnectionError(ErrCodeProtocol)
}
if _, err := hdec.Write(frag); err != nil {
return nil, ConnectionError(ErrCodeCompression)
}

View File

@ -1,30 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.11
// +build go1.11
package http2
import (
"net/http/httptrace"
"net/textproto"
)
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
return trace != nil && trace.WroteHeaderField != nil
}
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
if trace != nil && trace.WroteHeaderField != nil {
trace.WroteHeaderField(k, []string{v})
}
}
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
if trace != nil {
return trace.Got1xxResponse
}
return nil
}

View File

@ -1,27 +0,0 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.15
// +build go1.15
package http2
import (
"context"
"crypto/tls"
)
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
// connection.
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
dialer := &tls.Dialer{
Config: cfg,
}
cn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
return tlsCn, nil
}

View File

@ -1,17 +0,0 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
package http2
import (
"crypto/tls"
"net"
)
func tlsUnderlyingConn(tc *tls.Conn) net.Conn {
return tc.NetConn()
}

View File

@ -1,21 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.11
// +build !go1.11
package http2
import (
"net/http/httptrace"
"net/textproto"
)
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool { return false }
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {}
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
return nil
}

View File

@ -1,31 +0,0 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.15
// +build !go1.15
package http2
import (
"context"
"crypto/tls"
)
// dialTLSWithContext opens a TLS connection.
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
cn, err := tls.Dial(network, addr, cfg)
if err != nil {
return nil, err
}
if err := cn.Handshake(); err != nil {
return nil, err
}
if cfg.InsecureSkipVerify {
return cn, nil
}
if err := cn.VerifyHostname(cfg.ServerName); err != nil {
return nil, err
}
return cn, nil
}

View File

@ -1,17 +0,0 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package http2
import (
"crypto/tls"
"net"
)
func tlsUnderlyingConn(tc *tls.Conn) net.Conn {
return nil
}

View File

@ -77,7 +77,10 @@ func (p *pipe) Read(d []byte) (n int, err error) {
}
}
var errClosedPipeWrite = errors.New("write on closed buffer")
var (
errClosedPipeWrite = errors.New("write on closed buffer")
errUninitializedPipeWrite = errors.New("write on uninitialized buffer")
)
// Write copies bytes from p into the buffer and wakes a reader.
// It is an error to write more data than the buffer can hold.
@ -91,6 +94,12 @@ func (p *pipe) Write(d []byte) (n int, err error) {
if p.err != nil || p.breakErr != nil {
return 0, errClosedPipeWrite
}
// pipe.setBuffer is never invoked, leaving the buffer uninitialized.
// We shouldn't try to write to an uninitialized pipe,
// but returning an error is better than panicking.
if p.b == nil {
return 0, errUninitializedPipeWrite
}
return p.b.Write(d)
}

View File

@ -124,6 +124,7 @@ type Server struct {
// IdleTimeout specifies how long until idle clients should be
// closed with a GOAWAY frame. PING frames are not considered
// activity for the purposes of IdleTimeout.
// If zero or negative, there is no timeout.
IdleTimeout time.Duration
// MaxUploadBufferPerConnection is the size of the initial flow
@ -434,7 +435,7 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
// passes the connection off to us with the deadline already set.
// Write deadlines are set per stream in serverConn.newStream.
// Disarm the net.Conn write deadline here.
if sc.hs.WriteTimeout != 0 {
if sc.hs.WriteTimeout > 0 {
sc.conn.SetWriteDeadline(time.Time{})
}
@ -581,9 +582,11 @@ type serverConn struct {
advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
curClientStreams uint32 // number of open streams initiated by the client
curPushedStreams uint32 // number of open streams initiated by server push
curHandlers uint32 // number of running handler goroutines
maxClientStreamID uint32 // max ever seen from client (odd), or 0 if there have been no client requests
maxPushPromiseID uint32 // ID of the last push promise (even), or 0 if there have been no pushes
streams map[uint32]*stream
unstartedHandlers []unstartedHandler
initialStreamSendWindowSize int32
maxFrameSize int32
peerMaxHeaderListSize uint32 // zero means unknown (default)
@ -922,7 +925,7 @@ func (sc *serverConn) serve() {
sc.setConnState(http.StateActive)
sc.setConnState(http.StateIdle)
if sc.srv.IdleTimeout != 0 {
if sc.srv.IdleTimeout > 0 {
sc.idleTimer = time.AfterFunc(sc.srv.IdleTimeout, sc.onIdleTimer)
defer sc.idleTimer.Stop()
}
@ -981,6 +984,8 @@ func (sc *serverConn) serve() {
return
case gracefulShutdownMsg:
sc.startGracefulShutdownInternal()
case handlerDoneMsg:
sc.handlerDone()
default:
panic("unknown timer")
}
@ -1020,6 +1025,7 @@ var (
idleTimerMsg = new(serverMessage)
shutdownTimerMsg = new(serverMessage)
gracefulShutdownMsg = new(serverMessage)
handlerDoneMsg = new(serverMessage)
)
func (sc *serverConn) onSettingsTimer() { sc.sendServeMsg(settingsTimerMsg) }
@ -1632,7 +1638,7 @@ func (sc *serverConn) closeStream(st *stream, err error) {
delete(sc.streams, st.id)
if len(sc.streams) == 0 {
sc.setConnState(http.StateIdle)
if sc.srv.IdleTimeout != 0 {
if sc.srv.IdleTimeout > 0 {
sc.idleTimer.Reset(sc.srv.IdleTimeout)
}
if h1ServerKeepAlivesDisabled(sc.hs) {
@ -1892,10 +1898,12 @@ func (st *stream) copyTrailersToHandlerRequest() {
// onReadTimeout is run on its own goroutine (from time.AfterFunc)
// when the stream's ReadTimeout has fired.
func (st *stream) onReadTimeout() {
if st.body != nil {
// Wrap the ErrDeadlineExceeded to avoid callers depending on us
// returning the bare error.
st.body.CloseWithError(fmt.Errorf("%w", os.ErrDeadlineExceeded))
}
}
// onWriteTimeout is run on its own goroutine (from time.AfterFunc)
// when the stream's WriteTimeout has fired.
@ -2010,15 +2018,12 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
// similar to how the http1 server works. Here it's
// technically more like the http1 Server's ReadHeaderTimeout
// (in Go 1.8), though. That's a more sane option anyway.
if sc.hs.ReadTimeout != 0 {
if sc.hs.ReadTimeout > 0 {
sc.conn.SetReadDeadline(time.Time{})
if st.body != nil {
st.readDeadline = time.AfterFunc(sc.hs.ReadTimeout, st.onReadTimeout)
}
}
go sc.runHandler(rw, req, handler)
return nil
return sc.scheduleHandler(id, rw, req, handler)
}
func (sc *serverConn) upgradeRequest(req *http.Request) {
@ -2034,10 +2039,14 @@ func (sc *serverConn) upgradeRequest(req *http.Request) {
// Disable any read deadline set by the net/http package
// prior to the upgrade.
if sc.hs.ReadTimeout != 0 {
if sc.hs.ReadTimeout > 0 {
sc.conn.SetReadDeadline(time.Time{})
}
// This is the first request on the connection,
// so start the handler directly rather than going
// through scheduleHandler.
sc.curHandlers++
go sc.runHandler(rw, req, sc.handler.ServeHTTP)
}
@ -2108,7 +2117,7 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
st.flow.conn = &sc.flow // link to conn-level counter
st.flow.add(sc.initialStreamSendWindowSize)
st.inflow.init(sc.srv.initialStreamRecvWindowSize())
if sc.hs.WriteTimeout != 0 {
if sc.hs.WriteTimeout > 0 {
st.writeDeadline = time.AfterFunc(sc.hs.WriteTimeout, st.onWriteTimeout)
}
@ -2278,8 +2287,62 @@ func (sc *serverConn) newResponseWriter(st *stream, req *http.Request) *response
return &responseWriter{rws: rws}
}
type unstartedHandler struct {
streamID uint32
rw *responseWriter
req *http.Request
handler func(http.ResponseWriter, *http.Request)
}
// scheduleHandler starts a handler goroutine,
// or schedules one to start as soon as an existing handler finishes.
func (sc *serverConn) scheduleHandler(streamID uint32, rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) error {
sc.serveG.check()
maxHandlers := sc.advMaxStreams
if sc.curHandlers < maxHandlers {
sc.curHandlers++
go sc.runHandler(rw, req, handler)
return nil
}
if len(sc.unstartedHandlers) > int(4*sc.advMaxStreams) {
return sc.countError("too_many_early_resets", ConnectionError(ErrCodeEnhanceYourCalm))
}
sc.unstartedHandlers = append(sc.unstartedHandlers, unstartedHandler{
streamID: streamID,
rw: rw,
req: req,
handler: handler,
})
return nil
}
func (sc *serverConn) handlerDone() {
sc.serveG.check()
sc.curHandlers--
i := 0
maxHandlers := sc.advMaxStreams
for ; i < len(sc.unstartedHandlers); i++ {
u := sc.unstartedHandlers[i]
if sc.streams[u.streamID] == nil {
// This stream was reset before its goroutine had a chance to start.
continue
}
if sc.curHandlers >= maxHandlers {
break
}
sc.curHandlers++
go sc.runHandler(u.rw, u.req, u.handler)
sc.unstartedHandlers[i] = unstartedHandler{} // don't retain references
}
sc.unstartedHandlers = sc.unstartedHandlers[i:]
if len(sc.unstartedHandlers) == 0 {
sc.unstartedHandlers = nil
}
}
// Run on its own goroutine.
func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request, handler func(http.ResponseWriter, *http.Request)) {
defer sc.sendServeMsg(handlerDoneMsg)
didPanic := true
defer func() {
rw.rws.stream.cancelCtx()
@ -2487,7 +2550,6 @@ type responseWriterState struct {
wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet.
sentHeader bool // have we sent the header frame?
handlerDone bool // handler has finished
dirty bool // a Write failed; don't reuse this responseWriterState
sentContentLen int64 // non-zero if handler set a Content-Length header
wroteBytes int64
@ -2607,7 +2669,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
date: date,
})
if err != nil {
rws.dirty = true
return 0, err
}
if endStream {
@ -2628,7 +2689,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
if len(p) > 0 || endStream {
// only send a 0 byte DATA frame if we're ending the stream.
if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil {
rws.dirty = true
return 0, err
}
}
@ -2640,9 +2700,6 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
trailers: rws.trailers,
endStream: true,
})
if err != nil {
rws.dirty = true
}
return len(p), err
}
return len(p), nil
@ -2858,14 +2915,12 @@ func (rws *responseWriterState) writeHeader(code int) {
h.Del("Transfer-Encoding")
}
if rws.conn.writeHeaders(rws.stream, &writeResHeaders{
rws.conn.writeHeaders(rws.stream, &writeResHeaders{
streamID: rws.stream.id,
httpResCode: code,
h: h,
endStream: rws.handlerDone && !rws.hasTrailers(),
}) != nil {
rws.dirty = true
}
})
return
}
@ -2930,20 +2985,11 @@ func (w *responseWriter) write(lenData int, dataB []byte, dataS string) (n int,
func (w *responseWriter) handlerDone() {
rws := w.rws
dirty := rws.dirty
rws.handlerDone = true
w.Flush()
w.rws = nil
if !dirty {
// Only recycle the pool if all prior Write calls to
// the serverConn goroutine completed successfully. If
// they returned earlier due to resets from the peer
// there might still be write goroutines outstanding
// from the serverConn referencing the rws memory. See
// issue 20704.
responseWriterStatePool.Put(rws)
}
}
// Push errors.
var (
@ -3125,6 +3171,7 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
panic(fmt.Sprintf("newWriterAndRequestNoBody(%+v): %v", msg.url, err))
}
sc.curHandlers++
go sc.runHandler(rw, req, sc.handler.ServeHTTP)
return promisedID, nil
}

331
vendor/golang.org/x/net/http2/testsync.go generated vendored Normal file
View File

@ -0,0 +1,331 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package http2
import (
"context"
"sync"
"time"
)
// testSyncHooks coordinates goroutines in tests.
//
// For example, a call to ClientConn.RoundTrip involves several goroutines, including:
// - the goroutine running RoundTrip;
// - the clientStream.doRequest goroutine, which writes the request; and
// - the clientStream.readLoop goroutine, which reads the response.
//
// Using testSyncHooks, a test can start a RoundTrip and identify when all these goroutines
// are blocked waiting for some condition such as reading the Request.Body or waiting for
// flow control to become available.
//
// The testSyncHooks also manage timers and synthetic time in tests.
// This permits us to, for example, start a request and cause it to time out waiting for
// response headers without resorting to time.Sleep calls.
type testSyncHooks struct {
// active/inactive act as a mutex and condition variable.
//
// - neither chan contains a value: testSyncHooks is locked.
// - active contains a value: unlocked, and at least one goroutine is not blocked
// - inactive contains a value: unlocked, and all goroutines are blocked
active chan struct{}
inactive chan struct{}
// goroutine counts
total int // total goroutines
condwait map[*sync.Cond]int // blocked in sync.Cond.Wait
blocked []*testBlockedGoroutine // otherwise blocked
// fake time
now time.Time
timers []*fakeTimer
// Transport testing: Report various events.
newclientconn func(*ClientConn)
newstream func(*clientStream)
}
// testBlockedGoroutine is a blocked goroutine.
type testBlockedGoroutine struct {
f func() bool // blocked until f returns true
ch chan struct{} // closed when unblocked
}
func newTestSyncHooks() *testSyncHooks {
h := &testSyncHooks{
active: make(chan struct{}, 1),
inactive: make(chan struct{}, 1),
condwait: map[*sync.Cond]int{},
}
h.inactive <- struct{}{}
h.now = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
return h
}
// lock acquires the testSyncHooks mutex.
func (h *testSyncHooks) lock() {
select {
case <-h.active:
case <-h.inactive:
}
}
// waitInactive waits for all goroutines to become inactive.
func (h *testSyncHooks) waitInactive() {
for {
<-h.inactive
if !h.unlock() {
break
}
}
}
// unlock releases the testSyncHooks mutex.
// It reports whether any goroutines are active.
func (h *testSyncHooks) unlock() (active bool) {
// Look for a blocked goroutine which can be unblocked.
blocked := h.blocked[:0]
unblocked := false
for _, b := range h.blocked {
if !unblocked && b.f() {
unblocked = true
close(b.ch)
} else {
blocked = append(blocked, b)
}
}
h.blocked = blocked
// Count goroutines blocked on condition variables.
condwait := 0
for _, count := range h.condwait {
condwait += count
}
if h.total > condwait+len(blocked) {
h.active <- struct{}{}
return true
} else {
h.inactive <- struct{}{}
return false
}
}
// goRun starts a new goroutine.
func (h *testSyncHooks) goRun(f func()) {
h.lock()
h.total++
h.unlock()
go func() {
defer func() {
h.lock()
h.total--
h.unlock()
}()
f()
}()
}
// blockUntil indicates that a goroutine is blocked waiting for some condition to become true.
// It waits until f returns true before proceeding.
//
// Example usage:
//
// h.blockUntil(func() bool {
// // Is the context done yet?
// select {
// case <-ctx.Done():
// default:
// return false
// }
// return true
// })
// // Wait for the context to become done.
// <-ctx.Done()
//
// The function f passed to blockUntil must be non-blocking and idempotent.
func (h *testSyncHooks) blockUntil(f func() bool) {
if f() {
return
}
ch := make(chan struct{})
h.lock()
h.blocked = append(h.blocked, &testBlockedGoroutine{
f: f,
ch: ch,
})
h.unlock()
<-ch
}
// broadcast is sync.Cond.Broadcast.
func (h *testSyncHooks) condBroadcast(cond *sync.Cond) {
h.lock()
delete(h.condwait, cond)
h.unlock()
cond.Broadcast()
}
// broadcast is sync.Cond.Wait.
func (h *testSyncHooks) condWait(cond *sync.Cond) {
h.lock()
h.condwait[cond]++
h.unlock()
}
// newTimer creates a new fake timer.
func (h *testSyncHooks) newTimer(d time.Duration) timer {
h.lock()
defer h.unlock()
t := &fakeTimer{
hooks: h,
when: h.now.Add(d),
c: make(chan time.Time),
}
h.timers = append(h.timers, t)
return t
}
// afterFunc creates a new fake AfterFunc timer.
func (h *testSyncHooks) afterFunc(d time.Duration, f func()) timer {
h.lock()
defer h.unlock()
t := &fakeTimer{
hooks: h,
when: h.now.Add(d),
f: f,
}
h.timers = append(h.timers, t)
return t
}
func (h *testSyncHooks) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(ctx)
t := h.afterFunc(d, cancel)
return ctx, func() {
t.Stop()
cancel()
}
}
func (h *testSyncHooks) timeUntilEvent() time.Duration {
h.lock()
defer h.unlock()
var next time.Time
for _, t := range h.timers {
if next.IsZero() || t.when.Before(next) {
next = t.when
}
}
if d := next.Sub(h.now); d > 0 {
return d
}
return 0
}
// advance advances time and causes synthetic timers to fire.
func (h *testSyncHooks) advance(d time.Duration) {
h.lock()
defer h.unlock()
h.now = h.now.Add(d)
timers := h.timers[:0]
for _, t := range h.timers {
t := t // remove after go.mod depends on go1.22
t.mu.Lock()
switch {
case t.when.After(h.now):
timers = append(timers, t)
case t.when.IsZero():
// stopped timer
default:
t.when = time.Time{}
if t.c != nil {
close(t.c)
}
if t.f != nil {
h.total++
go func() {
defer func() {
h.lock()
h.total--
h.unlock()
}()
t.f()
}()
}
}
t.mu.Unlock()
}
h.timers = timers
}
// A timer wraps a time.Timer, or a synthetic equivalent in tests.
// Unlike time.Timer, timer is single-use: The timer channel is closed when the timer expires.
type timer interface {
C() <-chan time.Time
Stop() bool
Reset(d time.Duration) bool
}
// timeTimer implements timer using real time.
type timeTimer struct {
t *time.Timer
c chan time.Time
}
// newTimeTimer creates a new timer using real time.
func newTimeTimer(d time.Duration) timer {
ch := make(chan time.Time)
t := time.AfterFunc(d, func() {
close(ch)
})
return &timeTimer{t, ch}
}
// newTimeAfterFunc creates an AfterFunc timer using real time.
func newTimeAfterFunc(d time.Duration, f func()) timer {
return &timeTimer{
t: time.AfterFunc(d, f),
}
}
func (t timeTimer) C() <-chan time.Time { return t.c }
func (t timeTimer) Stop() bool { return t.t.Stop() }
func (t timeTimer) Reset(d time.Duration) bool { return t.t.Reset(d) }
// fakeTimer implements timer using fake time.
type fakeTimer struct {
hooks *testSyncHooks
mu sync.Mutex
when time.Time // when the timer will fire
c chan time.Time // closed when the timer fires; mutually exclusive with f
f func() // called when the timer fires; mutually exclusive with c
}
func (t *fakeTimer) C() <-chan time.Time { return t.c }
func (t *fakeTimer) Stop() bool {
t.mu.Lock()
defer t.mu.Unlock()
stopped := t.when.IsZero()
t.when = time.Time{}
return stopped
}
func (t *fakeTimer) Reset(d time.Duration) bool {
if t.c != nil || t.f == nil {
panic("fakeTimer only supports Reset on AfterFunc timers")
}
t.mu.Lock()
defer t.mu.Unlock()
t.hooks.lock()
defer t.hooks.unlock()
active := !t.when.IsZero()
t.when = t.hooks.now.Add(d)
if !active {
t.hooks.timers = append(t.hooks.timers, t)
}
return active
}

View File

@ -147,6 +147,12 @@ type Transport struct {
// waiting for their turn.
StrictMaxConcurrentStreams bool
// IdleConnTimeout is the maximum amount of time an idle
// (keep-alive) connection will remain idle before closing
// itself.
// Zero means no limit.
IdleConnTimeout time.Duration
// ReadIdleTimeout is the timeout after which a health check using ping
// frame will be carried out if no frame is received on the connection.
// Note that a ping response will is considered a received frame, so if
@ -178,6 +184,8 @@ type Transport struct {
connPoolOnce sync.Once
connPoolOrDef ClientConnPool // non-nil version of ConnPool
syncHooks *testSyncHooks
}
func (t *Transport) maxHeaderListSize() uint32 {
@ -302,7 +310,7 @@ type ClientConn struct {
readerErr error // set before readerDone is closed
idleTimeout time.Duration // or 0 for never
idleTimer *time.Timer
idleTimer timer
mu sync.Mutex // guards following
cond *sync.Cond // hold mu; broadcast on flow/closed changes
@ -344,6 +352,60 @@ type ClientConn struct {
werr error // first write error that has occurred
hbuf bytes.Buffer // HPACK encoder writes into this
henc *hpack.Encoder
syncHooks *testSyncHooks // can be nil
}
// Hook points used for testing.
// Outside of tests, cc.syncHooks is nil and these all have minimal implementations.
// Inside tests, see the testSyncHooks function docs.
// goRun starts a new goroutine.
func (cc *ClientConn) goRun(f func()) {
if cc.syncHooks != nil {
cc.syncHooks.goRun(f)
return
}
go f()
}
// condBroadcast is cc.cond.Broadcast.
func (cc *ClientConn) condBroadcast() {
if cc.syncHooks != nil {
cc.syncHooks.condBroadcast(cc.cond)
}
cc.cond.Broadcast()
}
// condWait is cc.cond.Wait.
func (cc *ClientConn) condWait() {
if cc.syncHooks != nil {
cc.syncHooks.condWait(cc.cond)
}
cc.cond.Wait()
}
// newTimer creates a new time.Timer, or a synthetic timer in tests.
func (cc *ClientConn) newTimer(d time.Duration) timer {
if cc.syncHooks != nil {
return cc.syncHooks.newTimer(d)
}
return newTimeTimer(d)
}
// afterFunc creates a new time.AfterFunc timer, or a synthetic timer in tests.
func (cc *ClientConn) afterFunc(d time.Duration, f func()) timer {
if cc.syncHooks != nil {
return cc.syncHooks.afterFunc(d, f)
}
return newTimeAfterFunc(d, f)
}
func (cc *ClientConn) contextWithTimeout(ctx context.Context, d time.Duration) (context.Context, context.CancelFunc) {
if cc.syncHooks != nil {
return cc.syncHooks.contextWithTimeout(ctx, d)
}
return context.WithTimeout(ctx, d)
}
// clientStream is the state for a single HTTP/2 stream. One of these
@ -425,7 +487,7 @@ func (cs *clientStream) abortStreamLocked(err error) {
// TODO(dneil): Clean up tests where cs.cc.cond is nil.
if cs.cc.cond != nil {
// Wake up writeRequestBody if it is waiting on flow control.
cs.cc.cond.Broadcast()
cs.cc.condBroadcast()
}
}
@ -435,7 +497,7 @@ func (cs *clientStream) abortRequestBodyWrite() {
defer cc.mu.Unlock()
if cs.reqBody != nil && cs.reqBodyClosed == nil {
cs.closeReqBodyLocked()
cc.cond.Broadcast()
cc.condBroadcast()
}
}
@ -445,10 +507,10 @@ func (cs *clientStream) closeReqBodyLocked() {
}
cs.reqBodyClosed = make(chan struct{})
reqBodyClosed := cs.reqBodyClosed
go func() {
cs.cc.goRun(func() {
cs.reqBody.Close()
close(reqBodyClosed)
}()
})
}
type stickyErrWriter struct {
@ -537,15 +599,6 @@ func authorityAddr(scheme string, authority string) (addr string) {
return net.JoinHostPort(host, port)
}
var retryBackoffHook func(time.Duration) *time.Timer
func backoffNewTimer(d time.Duration) *time.Timer {
if retryBackoffHook != nil {
return retryBackoffHook(d)
}
return time.NewTimer(d)
}
// RoundTripOpt is like RoundTrip, but takes options.
func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Response, error) {
if !(req.URL.Scheme == "https" || (req.URL.Scheme == "http" && t.AllowHTTP)) {
@ -573,13 +626,27 @@ func (t *Transport) RoundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Res
backoff := float64(uint(1) << (uint(retry) - 1))
backoff += backoff * (0.1 * mathrand.Float64())
d := time.Second * time.Duration(backoff)
timer := backoffNewTimer(d)
var tm timer
if t.syncHooks != nil {
tm = t.syncHooks.newTimer(d)
t.syncHooks.blockUntil(func() bool {
select {
case <-timer.C:
case <-tm.C():
case <-req.Context().Done():
default:
return false
}
return true
})
} else {
tm = newTimeTimer(d)
}
select {
case <-tm.C():
t.vlogf("RoundTrip retrying after failure: %v", roundTripErr)
continue
case <-req.Context().Done():
timer.Stop()
tm.Stop()
err = req.Context().Err()
}
}
@ -658,6 +725,9 @@ func canRetryError(err error) bool {
}
func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse bool) (*ClientConn, error) {
if t.syncHooks != nil {
return t.newClientConn(nil, singleUse, t.syncHooks)
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
@ -666,7 +736,7 @@ func (t *Transport) dialClientConn(ctx context.Context, addr string, singleUse b
if err != nil {
return nil, err
}
return t.newClientConn(tconn, singleUse)
return t.newClientConn(tconn, singleUse, nil)
}
func (t *Transport) newTLSConfig(host string) *tls.Config {
@ -732,10 +802,10 @@ func (t *Transport) maxEncoderHeaderTableSize() uint32 {
}
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) {
return t.newClientConn(c, t.disableKeepAlives())
return t.newClientConn(c, t.disableKeepAlives(), nil)
}
func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, error) {
func (t *Transport) newClientConn(c net.Conn, singleUse bool, hooks *testSyncHooks) (*ClientConn, error) {
cc := &ClientConn{
t: t,
tconn: c,
@ -750,10 +820,15 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
wantSettingsAck: true,
pings: make(map[[8]byte]chan struct{}),
reqHeaderMu: make(chan struct{}, 1),
syncHooks: hooks,
}
if hooks != nil {
hooks.newclientconn(cc)
c = cc.tconn
}
if d := t.idleConnTimeout(); d != 0 {
cc.idleTimeout = d
cc.idleTimer = time.AfterFunc(d, cc.onIdleTimeout)
cc.idleTimer = cc.afterFunc(d, cc.onIdleTimeout)
}
if VerboseLogs {
t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
@ -818,7 +893,7 @@ func (t *Transport) newClientConn(c net.Conn, singleUse bool) (*ClientConn, erro
return nil, cc.werr
}
go cc.readLoop()
cc.goRun(cc.readLoop)
return cc, nil
}
@ -826,7 +901,7 @@ func (cc *ClientConn) healthCheck() {
pingTimeout := cc.t.pingTimeout()
// We don't need to periodically ping in the health check, because the readLoop of ClientConn will
// trigger the healthCheck again if there is no frame received.
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
ctx, cancel := cc.contextWithTimeout(context.Background(), pingTimeout)
defer cancel()
cc.vlogf("http2: Transport sending health check")
err := cc.Ping(ctx)
@ -1018,7 +1093,7 @@ func (cc *ClientConn) forceCloseConn() {
if !ok {
return
}
if nc := tlsUnderlyingConn(tc); nc != nil {
if nc := tc.NetConn(); nc != nil {
nc.Close()
}
}
@ -1056,7 +1131,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
// Wait for all in-flight streams to complete or connection to close
done := make(chan struct{})
cancelled := false // guarded by cc.mu
go func() {
cc.goRun(func() {
cc.mu.Lock()
defer cc.mu.Unlock()
for {
@ -1068,9 +1143,9 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
if cancelled {
break
}
cc.cond.Wait()
cc.condWait()
}
}()
})
shutdownEnterWaitStateHook()
select {
case <-done:
@ -1080,7 +1155,7 @@ func (cc *ClientConn) Shutdown(ctx context.Context) error {
cc.mu.Lock()
// Free the goroutine above
cancelled = true
cc.cond.Broadcast()
cc.condBroadcast()
cc.mu.Unlock()
return ctx.Err()
}
@ -1118,7 +1193,7 @@ func (cc *ClientConn) closeForError(err error) {
for _, cs := range cc.streams {
cs.abortStreamLocked(err)
}
cc.cond.Broadcast()
cc.condBroadcast()
cc.mu.Unlock()
cc.closeConn()
}
@ -1215,6 +1290,10 @@ func (cc *ClientConn) decrStreamReservationsLocked() {
}
func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
return cc.roundTrip(req, nil)
}
func (cc *ClientConn) roundTrip(req *http.Request, streamf func(*clientStream)) (*http.Response, error) {
ctx := req.Context()
cs := &clientStream{
cc: cc,
@ -1229,9 +1308,23 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
respHeaderRecv: make(chan struct{}),
donec: make(chan struct{}),
}
go cs.doRequest(req)
cc.goRun(func() {
cs.doRequest(req)
})
waitDone := func() error {
if cc.syncHooks != nil {
cc.syncHooks.blockUntil(func() bool {
select {
case <-cs.donec:
case <-ctx.Done():
case <-cs.reqCancel:
default:
return false
}
return true
})
}
select {
case <-cs.donec:
return nil
@ -1292,7 +1385,24 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (*http.Response, error) {
return err
}
if streamf != nil {
streamf(cs)
}
for {
if cc.syncHooks != nil {
cc.syncHooks.blockUntil(func() bool {
select {
case <-cs.respHeaderRecv:
case <-cs.abort:
case <-ctx.Done():
case <-cs.reqCancel:
default:
return false
}
return true
})
}
select {
case <-cs.respHeaderRecv:
return handleResponseHeaders()
@ -1348,6 +1458,21 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
if cc.reqHeaderMu == nil {
panic("RoundTrip on uninitialized ClientConn") // for tests
}
var newStreamHook func(*clientStream)
if cc.syncHooks != nil {
newStreamHook = cc.syncHooks.newstream
cc.syncHooks.blockUntil(func() bool {
select {
case cc.reqHeaderMu <- struct{}{}:
<-cc.reqHeaderMu
case <-cs.reqCancel:
case <-ctx.Done():
default:
return false
}
return true
})
}
select {
case cc.reqHeaderMu <- struct{}{}:
case <-cs.reqCancel:
@ -1372,6 +1497,10 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
}
cc.mu.Unlock()
if newStreamHook != nil {
newStreamHook(cs)
}
// TODO(bradfitz): this is a copy of the logic in net/http. Unify somewhere?
if !cc.t.disableCompression() &&
req.Header.Get("Accept-Encoding") == "" &&
@ -1452,15 +1581,30 @@ func (cs *clientStream) writeRequest(req *http.Request) (err error) {
var respHeaderTimer <-chan time.Time
var respHeaderRecv chan struct{}
if d := cc.responseHeaderTimeout(); d != 0 {
timer := time.NewTimer(d)
timer := cc.newTimer(d)
defer timer.Stop()
respHeaderTimer = timer.C
respHeaderTimer = timer.C()
respHeaderRecv = cs.respHeaderRecv
}
// Wait until the peer half-closes its end of the stream,
// or until the request is aborted (via context, error, or otherwise),
// whichever comes first.
for {
if cc.syncHooks != nil {
cc.syncHooks.blockUntil(func() bool {
select {
case <-cs.peerClosed:
case <-respHeaderTimer:
case <-respHeaderRecv:
case <-cs.abort:
case <-ctx.Done():
case <-cs.reqCancel:
default:
return false
}
return true
})
}
select {
case <-cs.peerClosed:
return nil
@ -1609,7 +1753,7 @@ func (cc *ClientConn) awaitOpenSlotForStreamLocked(cs *clientStream) error {
return nil
}
cc.pendingRequests++
cc.cond.Wait()
cc.condWait()
cc.pendingRequests--
select {
case <-cs.abort:
@ -1871,10 +2015,26 @@ func (cs *clientStream) awaitFlowControl(maxBytes int) (taken int32, err error)
cs.flow.take(take)
return take, nil
}
cc.cond.Wait()
cc.condWait()
}
}
func validateHeaders(hdrs http.Header) string {
for k, vv := range hdrs {
if !httpguts.ValidHeaderFieldName(k) {
return fmt.Sprintf("name %q", k)
}
for _, v := range vv {
if !httpguts.ValidHeaderFieldValue(v) {
// Don't include the value in the error,
// because it may be sensitive.
return fmt.Sprintf("value for header %q", k)
}
}
}
return ""
}
var errNilRequestURL = errors.New("http2: Request.URI is nil")
// requires cc.wmu be held.
@ -1912,19 +2072,14 @@ func (cc *ClientConn) encodeHeaders(req *http.Request, addGzipHeader bool, trail
}
}
// Check for any invalid headers and return an error before we
// Check for any invalid headers+trailers and return an error before we
// potentially pollute our hpack state. (We want to be able to
// continue to reuse the hpack encoder for future requests)
for k, vv := range req.Header {
if !httpguts.ValidHeaderFieldName(k) {
return nil, fmt.Errorf("invalid HTTP header name %q", k)
}
for _, v := range vv {
if !httpguts.ValidHeaderFieldValue(v) {
// Don't include the value in the error, because it may be sensitive.
return nil, fmt.Errorf("invalid HTTP header value for header %q", k)
}
if err := validateHeaders(req.Header); err != "" {
return nil, fmt.Errorf("invalid HTTP header %s", err)
}
if err := validateHeaders(req.Trailer); err != "" {
return nil, fmt.Errorf("invalid HTTP trailer %s", err)
}
enumerateHeaders := func(f func(name, value string)) {
@ -2143,7 +2298,7 @@ func (cc *ClientConn) forgetStreamID(id uint32) {
}
// Wake up writeRequestBody via clientStream.awaitFlowControl and
// wake up RoundTrip if there is a pending request.
cc.cond.Broadcast()
cc.condBroadcast()
closeOnIdle := cc.singleUse || cc.doNotReuse || cc.t.disableKeepAlives() || cc.goAway != nil
if closeOnIdle && cc.streamsReserved == 0 && len(cc.streams) == 0 {
@ -2231,7 +2386,7 @@ func (rl *clientConnReadLoop) cleanup() {
cs.abortStreamLocked(err)
}
}
cc.cond.Broadcast()
cc.condBroadcast()
cc.mu.Unlock()
}
@ -2266,10 +2421,9 @@ func (rl *clientConnReadLoop) run() error {
cc := rl.cc
gotSettings := false
readIdleTimeout := cc.t.ReadIdleTimeout
var t *time.Timer
var t timer
if readIdleTimeout != 0 {
t = time.AfterFunc(readIdleTimeout, cc.healthCheck)
defer t.Stop()
t = cc.afterFunc(readIdleTimeout, cc.healthCheck)
}
for {
f, err := cc.fr.ReadFrame()
@ -2684,7 +2838,7 @@ func (rl *clientConnReadLoop) processData(f *DataFrame) error {
})
return nil
}
if !cs.firstByte {
if !cs.pastHeaders {
cc.logf("protocol error: received DATA before a HEADERS frame")
rl.endStreamError(cs, StreamError{
StreamID: f.StreamID,
@ -2867,7 +3021,7 @@ func (rl *clientConnReadLoop) processSettingsNoWrite(f *SettingsFrame) error {
for _, cs := range cc.streams {
cs.flow.add(delta)
}
cc.cond.Broadcast()
cc.condBroadcast()
cc.initialWindowSize = s.Val
case SettingHeaderTableSize:
@ -2911,9 +3065,18 @@ func (rl *clientConnReadLoop) processWindowUpdate(f *WindowUpdateFrame) error {
fl = &cs.flow
}
if !fl.add(int32(f.Increment)) {
// For stream, the sender sends RST_STREAM with an error code of FLOW_CONTROL_ERROR
if cs != nil {
rl.endStreamError(cs, StreamError{
StreamID: f.StreamID,
Code: ErrCodeFlowControl,
})
return nil
}
return ConnectionError(ErrCodeFlowControl)
}
cc.cond.Broadcast()
cc.condBroadcast()
return nil
}
@ -2955,24 +3118,38 @@ func (cc *ClientConn) Ping(ctx context.Context) error {
}
cc.mu.Unlock()
}
errc := make(chan error, 1)
go func() {
var pingError error
errc := make(chan struct{})
cc.goRun(func() {
cc.wmu.Lock()
defer cc.wmu.Unlock()
if err := cc.fr.WritePing(false, p); err != nil {
errc <- err
if pingError = cc.fr.WritePing(false, p); pingError != nil {
close(errc)
return
}
if err := cc.bw.Flush(); err != nil {
errc <- err
if pingError = cc.bw.Flush(); pingError != nil {
close(errc)
return
}
}()
})
if cc.syncHooks != nil {
cc.syncHooks.blockUntil(func() bool {
select {
case <-c:
case <-errc:
case <-ctx.Done():
case <-cc.readerDone:
default:
return false
}
return true
})
}
select {
case <-c:
return nil
case err := <-errc:
return err
case <-errc:
return pingError
case <-ctx.Done():
return ctx.Err()
case <-cc.readerDone:
@ -3141,9 +3318,17 @@ func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, err
}
func (t *Transport) idleConnTimeout() time.Duration {
// to keep things backwards compatible, we use non-zero values of
// IdleConnTimeout, followed by using the IdleConnTimeout on the underlying
// http1 transport, followed by 0
if t.IdleConnTimeout != 0 {
return t.IdleConnTimeout
}
if t.t1 != nil {
return t.t1.IdleConnTimeout
}
return 0
}
@ -3201,3 +3386,34 @@ func traceFirstResponseByte(trace *httptrace.ClientTrace) {
trace.GotFirstResponseByte()
}
}
func traceHasWroteHeaderField(trace *httptrace.ClientTrace) bool {
return trace != nil && trace.WroteHeaderField != nil
}
func traceWroteHeaderField(trace *httptrace.ClientTrace, k, v string) {
if trace != nil && trace.WroteHeaderField != nil {
trace.WroteHeaderField(k, []string{v})
}
}
func traceGot1xxResponseFunc(trace *httptrace.ClientTrace) func(int, textproto.MIMEHeader) error {
if trace != nil {
return trace.Got1xxResponse
}
return nil
}
// dialTLSWithContext uses tls.Dialer, added in Go 1.15, to open a TLS
// connection.
func (t *Transport) dialTLSWithContext(ctx context.Context, network, addr string, cfg *tls.Config) (*tls.Conn, error) {
dialer := &tls.Dialer{
Config: cfg,
}
cn, err := dialer.DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
tlsCn := cn.(*tls.Conn) // DialContext comment promises this will always succeed
return tlsCn, nil
}

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
package idna

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file.
//go:build go1.10
// +build go1.10
// Package idna implements IDNA2008 using the compatibility processing
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file.
//go:build !go1.10
// +build !go1.10
// Package idna implements IDNA2008 using the compatibility processing
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file.
//go:build !go1.18
// +build !go1.18
package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.10 && !go1.13
// +build go1.10,!go1.13
package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.13 && !go1.14
// +build go1.13,!go1.14
package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.14 && !go1.16
// +build go1.14,!go1.16
package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.16 && !go1.21
// +build go1.16,!go1.21
package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build go1.21
// +build go1.21
package idna

View File

@ -1,7 +1,6 @@
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
//go:build !go1.10
// +build !go1.10
package idna

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file.
//go:build !go1.16
// +build !go1.16
package idna

View File

@ -5,7 +5,6 @@
// license that can be found in the LICENSE file.
//go:build go1.16
// +build go1.16
package idna

View File

@ -1,30 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unsafeheader contains header declarations for the Go runtime's
// slice and string implementations.
//
// This package allows x/sys to use types equivalent to
// reflect.SliceHeader and reflect.StringHeader without introducing
// a dependency on the (relatively heavy) "reflect" package.
package unsafeheader
import (
"unsafe"
)
// Slice is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may change in a later release.
type Slice struct {
Data unsafe.Pointer
Len int
Cap int
}
// String is the runtime representation of a string.
// It cannot be used safely or portably and its representation may change in a later release.
type String struct {
Data unsafe.Pointer
Len int
}

View File

@ -2,9 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) && go1.9
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
// +build go1.9
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos
package unix

View File

@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build gc
// +build gc
#include "textflag.h"

View File

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (freebsd || netbsd || openbsd) && gc
// +build freebsd netbsd openbsd
// +build gc
#include "textflag.h"

View File

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && gc
// +build darwin dragonfly freebsd netbsd openbsd
// +build gc
#include "textflag.h"

View File

@ -3,8 +3,6 @@
// license that can be found in the LICENSE file.
//go:build (freebsd || netbsd || openbsd) && gc
// +build freebsd netbsd openbsd
// +build gc
#include "textflag.h"

Some files were not shown because too many files have changed in this diff Show More