Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
# Configuration file for golangci-lint
#
# https://github.com/golangci/golangci-lint
#
# fighting with false positives?
# https://github.com/golangci/golangci-lint#nolint
linters:
enable:
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
- gosec # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: true, auto-fix: false]
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
- deadcode # Finds unused code [fast: true, auto-fix: false]
- golint # Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes [fast: true, auto-fix: false]
disable:
# TODO(ross): fix errors reported by these checkers and enable them
- bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false]
- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
- dupl # Tool for code clone detection [fast: true, auto-fix: false]
- errcheck # Inspects source code for security problems [fast: true, auto-fix: false]
- gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false]
- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: false, auto-fix: false]
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
- interfacer # Linter that suggests narrower interface types [fast: false, auto-fix: false]
- lll # Reports long lines [fast: true, auto-fix: false]
- maligned # Tool to detect Go structs that would take less memory if their fields were sorted [fast: true, auto-fix: false]
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
- prealloc # Finds slice declarations that could potentially be preallocated [fast: true, auto-fix: false]
- scopelint # Scopelint checks for unpinned variables in go programs [fast: true, auto-fix: false]
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: false, auto-fix: false]
- structcheck # Finds unused struct fields [fast: true, auto-fix: false]
- stylecheck # Stylecheck is a replacement for golint [fast: false, auto-fix: false]
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
- unparam # Reports unused function parameters [fast: false, auto-fix: false]
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
- varcheck # Finds unused global variables and constants [fast: true, auto-fix: false]
linters-settings:
goimports:
local-prefixes: github.com/crewjam/saml
govet:
disable:
- shadow
enable:
- asmdecl
- assign
- atomic
- bools
- buildtag
- cgocall
- composites
- copylocks
- errorsas
- httpresponse
- loopclosure
- lostcancel
- nilfunc
- printf
- shift
- stdmethods
- structtag
- tests
- unmarshal
- unreachable
- unsafeptr
- unusedresult
issues:
exclude-use-default: false
exclude:
- G104 # 'Errors unhandled. (gosec)
+13
View File
@@ -0,0 +1,13 @@
language: go
env: GO111MODULE=on
before_script:
- curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.21.0
script:
- golangci-lint run
- go test -v ./...
go:
- tip
+23
View File
@@ -0,0 +1,23 @@
Copyright (c) 2016, Ross Kinder
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+152
View File
@@ -0,0 +1,152 @@
# httperr
[![GoDoc](https://godoc.org/github.com/crewjam/httperr?status.svg)](https://godoc.org/github.com/crewjam/httperr)
[![Build Status](https://travis-ci.org/crewjam/httperr.svg?branch=master)](https://travis-ci.org/crewjam/httperr)
Package httperr provides utilities for handling error conditions in http
clients and servers.
## Client
This package provides an http.Client that returns errors for requests that return
a status code >= 400. It lets you turn code like this:
```golang
func GetFoo() {
req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("api call failed: %d", resp.StatusCode)
}
// ....
}
```
Into code like this:
```golang
func GetFoo() {
req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
resp, err := httperr.Client().Do(req)
if err != nil {
return nil, err
}
// ....
}
```
Wow, three whole lines. Life changing, eh? But wait, there's more!
You can have the client parse structured errors returned from an API:
```golang
type APIError struct {
Message string `json:"message"`
Code string `json:"code"`
}
func (a APIError) Error() string {
// APIError must implement the Error interface
return fmt.Sprintf("%s (code %d)", a.Message, a.Code)
}
func GetFoo() {
client := httperr.Client(http.DefaultClient, httperr.JSON(APIError{}))
req, _ := http.NewRequest("GET", "https://api.example.com/foo", nil)
resp, err := client.Do(req)
if err != nil {
// If the server returned a status code >= 400, and the response was valid
// JSON for APIError, then err is an *APIErr.
return nil, err
}
// ....
}
```
## Server
Error handling in Go's http.Handler and http.HandlerFunc can be tricky. I often found myself wishing that we could just return an `err` and be done with things.
This package provides an adapter function which turns:
```golang
func (s *Server) getUser(w http.ResponseWriter, r *http.Request) {
remoteUser, err := s.Auth.RequireUser(w, r)
if err != nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
user, err := s.Storage.Get(remoteUser.Name)
if err != nil {
log.Printf("ERROR: cannot fetch user: %s", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(user)
}
```
Into this:
```golang
func (s *Server) getUser(w http.ResponseWriter, r *http.Request) error {
remoteUser, err := s.Auth.RequireUser(w, r)
if err != nil {
return httperr.Unauthorized
}
user, err := s.Storage.Get(remoteUser.Name)
if err != nil {
return err
}
return json.NewEncoder(w).Encode(user)
}
```
Life changing? Probably not, but it seems to remove a lot of redundancy and make control flow in web servers simpler.
You can also wrap your calls with middleware that allow you to provide custom handling of errors that are returned from your handlers, but also >= 400 status codes issued by handlers that don't return errors.
```golang
htmlErrorTmpl := template.Must(template.New("err").Parse(errorTemplate))
handler := httperr.Middleware{
OnError: func(w http.ResponseWriter, r *http.Request, err error) error {
log.Printf("REQUEST ERROR: %s", err)
if acceptHeaderContainsTextHTML(r) {
htmlErrorTmpl.Execute(w, struct{ Error error }{Error: err})
return nil // nil means we've handled the error
}
return err // fall back to the default
},
Handler: httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
if r.Method != "POST" {
return httperr.MethodNotAllowed
}
var reqBody RequestBody
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
return httperr.Public{
StatusCode: http.StatusBadRequest,
Err: err,
}
}
if reqBody.Count <= 0 {
// The client won't see this, instead OnError will be called with a httperr.Response containing
// the response. The OnError function can decide to write the error, or replace it with it's own.
w.WriteHeader(http.StatusConflict)
fmt.Fprintln(w, "an obscure internal error happened, but the user doesn't want to see this.")
return nil
}
// ...
return nil
}),
}
```
+120
View File
@@ -0,0 +1,120 @@
package httperr
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"reflect"
)
// ClientArg is an argument to Client
type ClientArg func(xport *Transport)
// Client returns an http.Client that wraps client with
// an error handling transport.
func Client(next *http.Client, args ...ClientArg) *http.Client {
xport := Transport{Next: next.Transport}
for _, arg := range args {
arg(&xport)
}
rv := *next
rv.Transport = xport
return &rv
}
// DefaultClient returns an http.Client that wraps the default
// http.Client with an error handling transport.
func DefaultClient() *http.Client {
return Client(http.DefaultClient)
}
var _ http.RoundTripper = Transport{}
// Transport is an http.RoundTripper that intercepts responses where
// the StatusCode >= 400 and returns a Response{}.
//
// If ErrorFactory is specified it should return an error that can be used
// to unmarshal a JSON error response. This is useful when a web service
// offers structured error information. If the error structure cannot be
// unmarshalled, then a regular Response error is returned.
//
// type APIError struct {
// Code string `json:"code"`
// Message string `json:"message"`
// }
//
// func (a APIError) Error() string {
// return fmt.Sprintf("%s (%d)", a.Message, a.Code)
// }
//
// t := Transport{
// ErrorFactory: func() error {
// return &APIError{}
// },
// }
//
type Transport struct {
Next http.RoundTripper
OnError func(req *http.Request, resp *http.Response) error
}
// RoundTrip implements http.RoundTripper.
func (t Transport) RoundTrip(req *http.Request) (*http.Response, error) {
next := t.Next
if next == nil {
next = http.DefaultTransport
}
resp, err := next.RoundTrip(req)
if err != nil {
return nil, err
}
if resp.StatusCode < 400 {
return resp, nil
}
if t.OnError != nil {
if err := t.OnError(req, resp); err != nil {
return nil, err
}
}
return nil, Response(*resp)
}
// JSON returns a ClientArg that specifies a function that
// handles errors structured as a JSON object.
func JSON(errStruct error) ClientArg {
typ := reflect.TypeOf(errStruct)
if typ.Kind() != reflect.Struct {
panic("JSON() argument must be a structure")
}
e := reflect.New(typ).Interface()
_ = e.(error) // panic if errStruct
return func(xport *Transport) {
xport.OnError = func(req *http.Request, resp *http.Response) error {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
resp.Body = ioutil.NopCloser(bytes.NewReader(body))
jsonErrValue := reflect.New(typ)
unmarshalErr := json.Unmarshal(body, jsonErrValue.Interface())
if unmarshalErr == nil {
// jsonErrValue is a *Foo if errStruct == Foo{}
return jsonErrValue.Elem().Interface().(error)
}
// we failed to unmarshal the response body, so ignore the
// JSON error and proceed as if ErrorFactory was not provided.
return Response(*resp)
}
}
}
+73
View File
@@ -0,0 +1,73 @@
package httperr
var (
// BadRequest is an error that represents a static http.StatusBadRequest error
BadRequest = Value{StatusCode: 400}
// Unauthorized is an error that represents a static http.StatusUnauthorized error
Unauthorized = Value{StatusCode: 401}
// PaymentRequired is an error that represents a static http.StatusPaymentRequired error
PaymentRequired = Value{StatusCode: 402}
// Forbidden is an error that represents a static http.StatusForbidden error
Forbidden = Value{StatusCode: 403}
// NotFound is an error that represents a static http.StatusNotFound error
NotFound = Value{StatusCode: 404}
// MethodNotAllowed is an error that represents a static http.StatusMethodNotAllowed error
MethodNotAllowed = Value{StatusCode: 405}
// NotAcceptable is an error that represents a static http.StatusNotAcceptable error
NotAcceptable = Value{StatusCode: 406}
// ProxyAuthRequired is an error that represents a static http.StatusProxyAuthRequired error
ProxyAuthRequired = Value{StatusCode: 407}
// RequestTimeout is an error that represents a static http.StatusRequestTimeout error
RequestTimeout = Value{StatusCode: 408}
// Conflict is an error that represents a static http.StatusConflict error
Conflict = Value{StatusCode: 409}
// Gone is an error that represents a static http.StatusGone error
Gone = Value{StatusCode: 410}
// LengthRequired is an error that represents a static http.StatusLengthRequired error
LengthRequired = Value{StatusCode: 411}
// PreconditionFailed is an error that represents a static http.StatusPreconditionFailed error
PreconditionFailed = Value{StatusCode: 412}
// RequestEntityTooLarge is an error that represents a static http.StatusRequestEntityTooLarge error
RequestEntityTooLarge = Value{StatusCode: 413}
// RequestURITooLong is an error that represents a static http.StatusRequestURITooLong error
RequestURITooLong = Value{StatusCode: 414}
// UnsupportedMediaType is an error that represents a static http.StatusUnsupportedMediaType error
UnsupportedMediaType = Value{StatusCode: 415}
// RequestedRangeNotSatisfiable is an error that represents a static http.StatusRequestedRangeNotSatisfiable error
RequestedRangeNotSatisfiable = Value{StatusCode: 416}
// ExpectationFailed is an error that represents a static http.StatusExpectationFailed error
ExpectationFailed = Value{StatusCode: 417}
// Teapot is an error that represents a static http.StatusTeapot error
Teapot = Value{StatusCode: 418}
// TooManyRequests is an error that represents a static http.StatusTooManyRequests error
TooManyRequests = Value{StatusCode: 429}
// InternalServerError is an error that represents a static http.StatusInternalServerError error
InternalServerError = Value{StatusCode: 500}
// NotImplemented is an error that represents a static http.StatusNotImplemented error
NotImplemented = Value{StatusCode: 501}
// BadGateway is an error that represents a static http.StatusBadGateway error
BadGateway = Value{StatusCode: 502}
// ServiceUnavailable is an error that represents a static http.StatusServiceUnavailable error
ServiceUnavailable = Value{StatusCode: 503}
// GatewayTimeout is an error that represents a static http.StatusGatewayTimeout error
GatewayTimeout = Value{StatusCode: 504}
// HTTPVersionNotSupported is an error that represents a static http.StatusHTTPVersionNotSupported error
HTTPVersionNotSupported = Value{StatusCode: 505}
)
// New returns a new http error wrapping err with status statusCode.
func New(statusCode int, err error) error {
return Value{
StatusCode: statusCode,
Err: err,
}
}
// Public returns a new public http error wrapping err with status statusCode.
func Public(statusCode int, err error) error {
return Value{
Public: true,
StatusCode: statusCode,
Err: err,
}
}
+20
View File
@@ -0,0 +1,20 @@
package httperr
import "net/http"
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
type HandlerFunc func(http.ResponseWriter, *http.Request) error
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err := f(w, r); err != nil {
if v := r.Context().Value(onErrorIndex); v != nil {
v.(func(error))(err)
} else {
Write(w, r, err)
}
}
}
+56
View File
@@ -0,0 +1,56 @@
// Package httperr implements an error object that speaks HTTP.
package httperr
import (
"errors"
"net/http"
pkgerrors "github.com/pkg/errors"
)
type statusCodeAndTexter interface {
StatusCodeAndText() (int, string)
}
// StatusCodeAndText returns the status code and text of the error
func StatusCodeAndText(err error) (int, string) {
if err == nil {
return http.StatusOK, http.StatusText(http.StatusOK)
}
err = pkgerrors.Cause(err)
var scater statusCodeAndTexter
if errors.As(err, &scater) {
return scater.StatusCodeAndText()
}
return http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)
}
// Writer is an interface for things that know how to write themselves
// to an error response. This interface is implemented by Private and
// Public to provide default error pages.
type Writer interface {
error
WriteError(w http.ResponseWriter, r *http.Request)
}
// Write writes the specified error to w. If err is a Writer, then
// it's WriteError method is invoked to produce the response.
// Otherwise a generic "500 Internal Server Error" is written.
func Write(w http.ResponseWriter, r *http.Request, err error) {
err = pkgerrors.Cause(err)
var errWriter Writer
if errors.As(err, &errWriter) {
errWriter.WriteError(w, r)
return
}
genericErr := Value{
Err: err,
StatusCode: http.StatusInternalServerError,
}
genericErr.WriteError(w, r)
}
+62
View File
@@ -0,0 +1,62 @@
package httperr
import (
"context"
"net/http"
)
type onErrorIndexType int
const onErrorIndex onErrorIndexType = iota
// Middleware wraps the provided handler with middleware that captures errors which
// are returned from HandlerFunc, or reported via ReportError, and invokes the provided
// callback to render them. If the handler returns a status code >= 400, the response is
// captured and passed to OnError as a Response.
//
type Middleware struct {
// OnError is a function that is called then a request fails with an error. If this function
// returns nil, then the error is assumed to be handled. If it returns a non-nil error, then
// that error is written to the client with Write()
OnError func(w http.ResponseWriter, r *http.Request, err error) error
// Handler is the next handler
Handler http.Handler
}
func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var unwrappedWriter = w
var wrappedWriter *basicWriter
if m.OnError != nil {
wrappedWriter, w = wrapWriter(w)
}
var didCallOnError bool
r = r.WithContext(context.WithValue(r.Context(), onErrorIndex, func(err error) {
if m.OnError != nil {
didCallOnError = true
handlerErr := m.OnError(unwrappedWriter, r, err)
if handlerErr != nil {
Write(unwrappedWriter, r, handlerErr)
}
}
}))
m.Handler.ServeHTTP(w, r)
if wrappedWriter != nil && wrappedWriter.statusCode >= 400 && !didCallOnError {
err := Response(*wrappedWriter.copy)
handlerErr := m.OnError(unwrappedWriter, r, err)
if handlerErr != nil {
Write(unwrappedWriter, r, handlerErr)
}
}
}
// ReportError reports the error to the function given in
// OnError.
func ReportError(r *http.Request, err error) {
if v := r.Context().Value(onErrorIndex); v != nil {
v.(func(error))(err)
}
}
+42
View File
@@ -0,0 +1,42 @@
package httperr
import (
"io"
"net/http"
)
// Response is an alias for http.Response that implements
// the error interface. Example:
//
// resp, err := http.Get("http://www.example.com")
// if err != nil {
// return err
// }
// if resp.StatusCode != http.StatusOK {
// return httperr.Response(*resp)
// }
// // ...
//
type Response http.Response
func (re Response) Error() string {
statusText := re.Status
if statusText == "" {
statusText = http.StatusText(re.StatusCode)
}
return statusText
}
// WriteError copies the Response to the ResponseWriter.
func (re Response) WriteError(w http.ResponseWriter, r *http.Request) {
for k, vv := range re.Header {
for _, v := range vv {
w.Header().Add(k, v)
}
}
w.WriteHeader(re.StatusCode)
io.Copy(w, re.Body)
}
var _ error = Response{}
var _ Writer = Response{}
+67
View File
@@ -0,0 +1,67 @@
// Package httperr implements an error object that speaks HTTP.
package httperr
import (
"fmt"
"net/http"
)
// Value is an Error that returns that status and code
// provided, and reveals the underlying wrapper error to
// the caller. The text of the error is rendered to the
// client in the body of the response, as well as in
// the X-Error header.
type Value struct {
Err error // the underlying error
StatusCode int // the HTTP status code. If not supplied, http.StatusInternalServerError is used.
Status string // the HTTP status text. If not supplied, http.StatusText(http.StatusCode) is used.
Public bool
Header http.Header // extra headers to add to the response (optional)
}
// StatusCodeAndText returns the status code and text of the error
func (e Value) StatusCodeAndText() (int, string) {
if e.StatusCode == 0 {
e.StatusCode = http.StatusInternalServerError
}
if e.Status == "" {
if e.Err != nil && e.Public {
e.Status = e.Err.Error()
} else {
e.Status = http.StatusText(e.StatusCode)
}
}
return e.StatusCode, e.Status
}
func (e Value) Error() string {
statusCode, statusText := StatusCodeAndText(e)
if e.Public {
return fmt.Sprintf("%d %s", statusCode, statusText)
}
return fmt.Sprintf("%d %s: %s", statusCode, statusText, e.Err.Error())
}
// WriteError writes an error response to w using the specified status code.
func (e Value) WriteError(w http.ResponseWriter, r *http.Request) {
for key, values := range e.Header {
w.Header().Del(key) // overwrite headers already in the response with the ones specified
for _, value := range values {
w.Header().Add(key, value)
}
}
code, message := e.StatusCodeAndText()
http.Error(w, message, code)
}
// Unwrap unwraps the Value error and returns the underlying error`
func (e Value) Unwrap() error {
return e.Err
}
var _ error = Value{}
var _ Writer = Value{}
var _ statusCodeAndTexter = Value{}
+119
View File
@@ -0,0 +1,119 @@
package httperr
// Note: The writer proxies in this file are a heavily modified version of
// code bearing the following message:
//
// Copyright (c) 2014, 2015, 2016 Carl Jackson (carl@avtok.com)
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import (
"bufio"
"bytes"
"io/ioutil"
"net"
"net/http"
)
// wrapWriter wraps an http.ResponseWriter, returning a proxy that
// tracks the response.
func wrapWriter(w http.ResponseWriter) (*basicWriter, http.ResponseWriter) {
_, isCloseNotifier := w.(http.CloseNotifier)
_, isFlusher := w.(http.Flusher)
_, isHijacker := w.(http.Hijacker)
bw := basicWriter{ResponseWriter: w}
if isCloseNotifier && isFlusher && isHijacker {
rv := fancyWriter{bw}
return &rv.basicWriter, &rv
}
if isFlusher {
rv := flushWriter{bw}
return &rv.basicWriter, &rv
}
return &bw, &bw
}
type basicWriter struct {
http.ResponseWriter
statusCode int
copy *http.Response
body *bytes.Buffer
}
func (b *basicWriter) WriteHeader(code int) {
b.statusCode = code
if code < 400 {
b.ResponseWriter.WriteHeader(code)
return
}
b.copy = &http.Response{
StatusCode: code,
Header: b.ResponseWriter.Header(),
}
}
func (b *basicWriter) Write(buf []byte) (int, error) {
if b.copy == nil {
return b.ResponseWriter.Write(buf)
}
if b.body == nil {
b.body = &bytes.Buffer{}
b.copy.Body = ioutil.NopCloser(b.body)
}
return b.body.Write(buf)
}
type fancyWriter struct {
basicWriter
}
func (f *fancyWriter) CloseNotify() <-chan bool {
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
return cn.CloseNotify()
}
func (f *fancyWriter) Flush() {
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
func (f *fancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
return hj.Hijack()
}
var _ http.CloseNotifier = &fancyWriter{}
var _ http.Flusher = &fancyWriter{}
var _ http.Hijacker = &fancyWriter{}
type flushWriter struct {
basicWriter
}
func (f *flushWriter) Flush() {
fl := f.basicWriter.ResponseWriter.(http.Flusher)
fl.Flush()
}
var _ http.Flusher = &flushWriter{}