Documentation
¶
Overview ¶
Package apirouter provides lightweight HTTP middleware for CORS handling, structured logging, and standardized API response formatting.
It is designed to integrate seamlessly with Julien Schmidt's httprouter and leverages the go-logger package by MrZ for consistent logging across services.
Index ¶
- Constants
- Variables
- func Check(w http.ResponseWriter, r *http.Request, sessionSecret, issuer string, ...) (authenticated bool, req *http.Request, err error)
- func ClearToken(w http.ResponseWriter, req *http.Request)
- func CreateToken(sessionSecret, userID, issuer, sessionID string, expiration time.Duration) (string, error)
- func FilterMap(params *parameters.Params, filterOutFields []string) (filtered *parameters.Params)
- func FindString(needle string, haystack []string) int
- func GetAuthToken(req *http.Request) (token string, ok bool)
- func GetClientIPAddress(req *http.Request) string
- func GetCustomData(req *http.Request) (data interface{})
- func GetIPFromRequest(req *http.Request) (ip string, ok bool)
- func GetParams(req *http.Request) *parameters.Params
- func GetRequestID(req *http.Request) (id string, ok bool)
- func GetTokenFromHeader(w http.ResponseWriter) string
- func GetTokenFromHeaderFromRequest(req *http.Request) string
- func GetTokenFromResponse(res *http.Response) string
- func JSONEncode(e *json.Encoder, objects interface{}, allowed []string) error
- func JSONEncodeHierarchy(w io.Writer, objects interface{}, allowed interface{}) error
- func NoCache(w http.ResponseWriter, req *http.Request)
- func PermitParams(params *parameters.Params, allowedKeys []string)
- func RespondWith(w http.ResponseWriter, _ *http.Request, status int, data interface{})
- func ReturnJSONEncode(w http.ResponseWriter, code int, e *json.Encoder, objects interface{}, ...) (err error)
- func ReturnResponse(w http.ResponseWriter, req *http.Request, code int, data interface{})
- func SetAuthToken(req *http.Request, authToken string) *http.Request
- func SetCustomData(req *http.Request, data interface{}) *http.Request
- func SetOnRequest(req *http.Request, keyName paramRequestKey, value interface{}) *http.Request
- func SetTokenHeader(w http.ResponseWriter, r *http.Request, token string, expiration time.Duration)
- func SnakeCase(str string) string
- func StandardHandlerToHandle(next http.Handler) httprouter.Handle
- type APIError
- type APIResponseWriter
- type AllowedKeys
- type Claims
- type InternalStack
- type LoggerInterface
- type Middleware
- type Router
- func (r *Router) BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string, ...) httprouter.Handle
- func (r *Router) Request(h httprouter.Handle) httprouter.Handle
- func (r *Router) RequestNoLogging(h httprouter.Handle) httprouter.Handle
- func (r *Router) SetCrossOriginHeaders(w http.ResponseWriter, req *http.Request, _ httprouter.Params)
- type Stack
Examples ¶
Constants ¶
const ( LogErrorFormat string = "request_id=\"%s\" ip_address=\"%s\" type=\"%s\" internal_message=\"%s\" code=%d\n" LogPanicFormat string = "request_id=\"%s\" method=\"%s\" path=\"%s\" type=\"%s\" error_message=\"%s\" stack_trace=\"%s\"\n" LogParamsFormat string = "request_id=\"%s\" method=\"%s\" path=\"%s\" ip_address=\"%s\" user_agent=\"%s\" params=\"%v\"\n" LogTimeFormat string = "request_id=\"%s\" method=\"%s\" path=\"%s\" ip_address=\"%s\" user_agent=\"%s\" service=%dms status=%d\n" )
Log formats for the request
const ( // AuthorizationHeader is the auth header AuthorizationHeader = "Authorization" // AuthorizationBearer is the second part of the auth header AuthorizationBearer = "Bearer" // CookieName is for the secure cookie that also has the JWT token CookieName = "jwt_token" )
const ( // ErrCodeUnknown unknown error code (example) ErrCodeUnknown int = 600 // StatusCodeUnknown unknown HTTP status code (example) StatusCodeUnknown int = 600 )
Variables ¶
var ErrClaimsValidationFailed = errors.New("claims failed validation")
ErrClaimsValidationFailed is when the claim's validation has failed
var ErrHeaderInvalid = errors.New("authorization header was mal-formatted or missing")
ErrHeaderInvalid is when the header is missing or invalid
var ErrInvalidSessionID = errors.New("invalid session id detected")
ErrInvalidSessionID is when the session id is invalid or missing
var ErrInvalidSigningMethod = errors.New("invalid signing method")
ErrInvalidSigningMethod is when the signing method is invalid
var ErrInvalidUserID = errors.New("invalid user id detected")
ErrInvalidUserID is when the user ID is invalid or missing
var ErrIssuerMismatch = errors.New("issuer did not match")
ErrIssuerMismatch is when the issuer does not match the system issuer
var ErrJWTInvalid = errors.New("jwt was invalid")
ErrJWTInvalid is when the JWT payload is invalid
Functions ¶
func Check ¶ added in v0.5.0
func Check(w http.ResponseWriter, r *http.Request, sessionSecret, issuer string, sessionAge time.Duration) (authenticated bool, req *http.Request, err error)
Check will check if the JWT is present and valid in the request and then extend the token
func ClearToken ¶ added in v0.5.0
func ClearToken(w http.ResponseWriter, req *http.Request)
ClearToken will remove the token from the response and request
func CreateToken ¶ added in v0.5.0
func CreateToken(sessionSecret, userID, issuer, sessionID string, expiration time.Duration) (string, error)
CreateToken will make the claims, and then make/sign the token
func FilterMap ¶ added in v0.1.12
func FilterMap(params *parameters.Params, filterOutFields []string) (filtered *parameters.Params)
FilterMap will filter the parameters and not log parameters with sensitive data. To add more parameters - see the if in the loop.
func FindString ¶ added in v0.0.10
FindString returns the index of the first instance of needle in the array or -1 if it could not be found
func GetAuthToken ¶ added in v0.0.23
GetAuthToken gets the stored authentication token from the request
func GetClientIPAddress ¶
GetClientIPAddress gets the client ip address
func GetCustomData ¶ added in v0.0.24
GetCustomData gets the stored custom data
func GetIPFromRequest ¶ added in v0.0.15
GetIPFromRequest gets the stored ip from the request if found
func GetParams ¶
GetParams gets the params from the http request (parsed once on log request) Helper method for the parameters package
func GetRequestID ¶ added in v0.0.15
GetRequestID gets the stored request id from the request
func GetTokenFromHeader ¶ added in v0.5.0
func GetTokenFromHeader(w http.ResponseWriter) string
GetTokenFromHeader will get the token value from the header
func GetTokenFromHeaderFromRequest ¶ added in v0.5.0
GetTokenFromHeaderFromRequest will get the token value from the request
func GetTokenFromResponse ¶ added in v0.5.0
GetTokenFromResponse will get the token value from the HTTP response
func JSONEncode ¶ added in v0.0.10
JSONEncode will encode only the allowed fields of the models
func JSONEncodeHierarchy ¶ added in v0.0.10
JSONEncodeHierarchy will execute JSONEncode for multiple nested objects
func NoCache ¶ added in v0.0.22
func NoCache(w http.ResponseWriter, req *http.Request)
NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent a router (or subrouter) from being cached by an upstream proxy and/or client.
As per http://wiki.nginx.org/HttpProxyModule - NoCache sets:
Expires: Thu, 01 Jan 1970 00:00:00 UTC Cache-Control: no-cache, private, max-age=0 X-Accel-Expires: 0 Pragma: no-cache (for HTTP/1.0 proxies/clients)
func PermitParams ¶ added in v0.0.17
func PermitParams(params *parameters.Params, allowedKeys []string)
PermitParams will remove all keys that not allowed Helper method for the parameters package
func RespondWith ¶ added in v0.11.0
func RespondWith(w http.ResponseWriter, _ *http.Request, status int, data interface{})
RespondWith writes a JSON response with the specified status code and data to the ResponseWriter. It sets the "Content-Type" header to "application/json; charset=utf-8". The data is serialized to JSON.
If data is an error, it responds with a JSON object {"error": <error message>}. If data is nil and the status is an error (>= 400), it responds with {"error": <StatusText>, "code": <status>}. If the status is 204 (No Content) or 304 (Not Modified), no response body is sent.
This function ensures a single response per request and is safe for use in HTTP handlers.
func ReturnJSONEncode ¶ added in v0.0.12
func ReturnJSONEncode(w http.ResponseWriter, code int, e *json.Encoder, objects interface{}, allowed []string) (err error)
ReturnJSONEncode is a mixture of ReturnResponse and JSONEncode
func ReturnResponse ¶ added in v0.0.3
func ReturnResponse(w http.ResponseWriter, req *http.Request, code int, data interface{})
ReturnResponse helps return a status code and message to the end user deprecated: use RespondWith instead
func SetAuthToken ¶ added in v0.0.23
SetAuthToken set the authentication token on the request
func SetCustomData ¶ added in v0.0.24
SetCustomData set the custom data / user profile / permissions / etc
func SetOnRequest ¶ added in v0.0.23
SetOnRequest will set the value on the request with the given key
func SetTokenHeader ¶ added in v0.5.0
SetTokenHeader will set the authentication token on the response and set a cookie
func SnakeCase ¶ added in v0.0.10
SnakeCase takes a camelCaseWord and breaks it into a camel_case_word
func StandardHandlerToHandle ¶ added in v0.0.7
func StandardHandlerToHandle(next http.Handler) httprouter.Handle
StandardHandlerToHandle converts a standard middleware to Julien handle version
Types ¶
type APIError ¶
type APIError struct { Code int `json:"code" url:"code"` // Associated error code Data interface{} `json:"data" url:"data"` // Arbitrary data that is relevant InternalMessage string `json:"-" url:"-"` // An internal message for engineers IPAddress string `json:"ip_address" url:"ip_address"` // Current IP of user Method string `json:"method" url:"method"` // Method requested (IE: POST) PublicMessage string `json:"message" url:"message"` // Public error message RequestGUID string `json:"request_guid" url:"request_guid"` // Unique Request ID for tracking StatusCode int `json:"status_code" url:"status_code"` // Associated HTTP status code (should be in request as well) URL string `json:"url" url:"url"` // Requesting URL }
APIError is the enriched error message for API related errors
func ErrorFromRequest ¶ added in v0.0.16
func ErrorFromRequest(req *http.Request, internalMessage, publicMessage string, errorCode, statusCode int, data interface{}) *APIError
ErrorFromRequest gives an error without a response writer using the request
func ErrorFromResponse ¶ added in v0.0.16
func ErrorFromResponse(w *APIResponseWriter, internalMessage, publicMessage string, errorCode, statusCode int, data interface{}) *APIError
ErrorFromResponse generates a new error struct using CustomResponseWriter from LogRequest()
Example ¶
ExampleErrorFromResponse example using ErrorFromResponse()
w := setupTest() err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`) fmt.Println(err.Error())
Output: public message
func (*APIError) Error ¶
Error returns the string error message (only public message)
Example ¶
ExampleAPIError_Error example using Error()
w := setupTest() err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`) fmt.Println(err.Error())
Output: public message
func (*APIError) ErrorCode ¶
ErrorCode returns the error code
Example ¶
ExampleAPIError_ErrorCode example using ErrorCode()
w := setupTest() err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`) fmt.Println(err.ErrorCode())
Output: 600
func (*APIError) Internal ¶
Internal returns the string error message (only internal message)
Example ¶
ExampleAPIError_Internal example using Internal()
w := setupTest() err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`) fmt.Println(err.Internal())
Output: internal message
func (*APIError) JSON ¶
JSON returns the entire public version of the error message
Example ¶
ExampleAPIError_JSON example using JSON()
w := setupTest() err := ErrorFromResponse(w, "internal message", "public message", ErrCodeUnknown, StatusCodeUnknown, `{"something":"else"}`) str, _ := err.JSON() fmt.Println(str)
Output: {"code":600,"data":"{\"something\":\"else\"}","ip_address":"127.0.0.1","method":"GET","message":"public message","request_guid":"unique-guid-per-user","status_code":600,"url":"/this/path"}
type APIResponseWriter ¶
type APIResponseWriter struct { http.ResponseWriter Buffer bytes.Buffer `json:"-" url:"-"` CacheIdentifier []string `json:"cache_identifier" url:"cache_identifier"` CacheTTL time.Duration `json:"cache_ttl" url:"cache_ttl"` IPAddress string `json:"ip_address" url:"ip_address"` Method string `json:"method" url:"method"` NoWrite bool `json:"no_write" url:"no_write"` RequestID string `json:"request_id" url:"request_id"` Status int `json:"status" url:"status"` URL string `json:"url" url:"url"` UserAgent string `json:"user_agent" url:"user_agent"` }
APIResponseWriter wraps the ResponseWriter and stores the status of the request. It is used by the LogRequest middleware
func (*APIResponseWriter) AddCacheIdentifier ¶
func (r *APIResponseWriter) AddCacheIdentifier(identifier string)
AddCacheIdentifier add cache identifier to the response writer
func (*APIResponseWriter) Header ¶
func (r *APIResponseWriter) Header() http.Header
Header returns the http.Header that will be written to the response
func (*APIResponseWriter) StatusCode ¶ added in v0.0.5
func (r *APIResponseWriter) StatusCode() int
StatusCode give a way to get the status code
func (*APIResponseWriter) Write ¶
func (r *APIResponseWriter) Write(data []byte) (int, error)
Write writes the data out to the client, if WriteHeader was not called, it will write status http.StatusOK (200)
func (*APIResponseWriter) WriteHeader ¶
func (r *APIResponseWriter) WriteHeader(status int)
WriteHeader will write the header to the client, setting the status code
type AllowedKeys ¶ added in v0.0.10
type AllowedKeys map[string]interface{}
AllowedKeys is for allowed keys
type Claims ¶ added in v0.5.0
type Claims struct { jwt.RegisteredClaims // Updated to use RegisteredClaims UserID string `json:"user_id"` // The user ID set on the claims }
Claims is our custom JWT claims
func (Claims) CreateToken ¶ added in v0.5.0
CreateToken will make a token from claims
type InternalStack ¶ added in v0.0.18
type InternalStack struct {
// contains filtered or unexported fields
}
InternalStack internal stack type
func NewStack ¶ added in v0.0.6
func NewStack() *InternalStack
NewStack will create an InternalStack struct
func (*InternalStack) Use ¶ added in v0.0.18
func (s *InternalStack) Use(mw Middleware)
Use adds the middleware to the list
func (*InternalStack) Wrap ¶ added in v0.0.18
func (s *InternalStack) Wrap(fn httprouter.Handle) httprouter.Handle
Wrap wraps the router
type LoggerInterface ¶ added in v0.7.0
type LoggerInterface interface {
Printf(format string, v ...interface{})
}
LoggerInterface is the logger interface
type Middleware ¶ added in v0.0.6
type Middleware func(httprouter.Handle) httprouter.Handle
Middleware is the Handle implementation
func StandardHandlerToMiddleware ¶ added in v0.0.8
func StandardHandlerToMiddleware(next http.Handler) Middleware
StandardHandlerToMiddleware converts standard middleware to type Middleware
type Router ¶ added in v0.0.2
type Router struct { AccessControlExposeHeaders string `json:"access_control_expose_headers" url:"access_control_expose_headers"` // Allow specific headers for cors CrossOriginAllowCredentials bool `json:"cross_origin_allow_credentials" url:"cross_origin_allow_credentials"` // Allow credentials for BasicAuth() CrossOriginAllowHeaders string `json:"cross_origin_allow_headers" url:"cross_origin_allow_headers"` // Allowed headers CrossOriginAllowMethods string `json:"cross_origin_allow_methods" url:"cross_origin_allow_methods"` // Allowed methods CrossOriginAllowOrigin string `json:"cross_origin_allow_origin" url:"cross_origin_allow_origin"` // Custom value for allow origin CrossOriginAllowOriginAll bool `json:"cross_origin_allow_origin_all" url:"cross_origin_allow_origin_all"` // Allow all origins CrossOriginEnabled bool `json:"cross_origin_enabled" url:"cross_origin_enabled"` // Enable or Disable CrossOrigin FilterFields []string `json:"filter_fields" url:"filter_fields"` // Filter out protected fields from logging HTTPRouter *nrhttprouter.Router `json:"-" url:"-"` // NewRelic wrapper for J Schmidt's httprouter Logger LoggerInterface `json:"-" url:"-"` // Logger interface SkipLoggingPaths []string `json:"skip_logging_paths" url:"skip_logging_paths"` // Skip logging on these paths (IE: /health) // contains filtered or unexported fields }
Router is the configuration for the middleware service
func New ¶
func New() *Router
New returns a router middleware configuration to use for all future requests
func NewWithNewRelic ¶ added in v0.4.6
func NewWithNewRelic(app *newrelic.Application) *Router
NewWithNewRelic returns a router middleware configuration with NewRelic enabled
func (*Router) BasicAuth ¶ added in v0.0.2
func (r *Router) BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string, errorResponse interface{}) httprouter.Handle
BasicAuth wraps a request for Basic Authentication (RFC 2617)
func (*Router) Request ¶ added in v0.0.2
func (r *Router) Request(h httprouter.Handle) httprouter.Handle
Request will write the request to the logs before and after calling the handler
func (*Router) RequestNoLogging ¶ added in v0.0.2
func (r *Router) RequestNoLogging(h httprouter.Handle) httprouter.Handle
RequestNoLogging will just call the handler without any logging Used for API calls that do not require any logging overhead
func (*Router) SetCrossOriginHeaders ¶ added in v0.0.4
func (r *Router) SetCrossOriginHeaders(w http.ResponseWriter, req *http.Request, _ httprouter.Params)
SetCrossOriginHeaders sets the cross-origin headers if enabled todo: combine this method and the GlobalOPTIONS http.HandlerFunc() method (@mrz had an issue combining)
type Stack ¶ added in v0.0.6
type Stack interface { /* Adds middleware to the InternalStack. MWs will be called in the same order that they are added such that: Use(Request ID Middleware) Use(Request Timing Middleware) would result in the request id middleware being the outermost layer, called first, before the timing middleware. */ // Use for adding new middlewares Use(m Middleware) /* Wraps a given handle with the current InternalStack from the result of Use() calls. */ // Wrap wraps the router Wrap(h httprouter.Handle) httprouter.Handle }
Stack is the interface for middleware