Documentation
¶
Index ¶
- Variables
- func ErrorMapping(ctx context.Context, err error, dto *rfc7807.DTO)
- func IsSuccess[V int | *http.Response](v V) bool
- func LookupRequest(ctx context.Context) (*http.Request, bool)
- func Mount(mux Multiplexer, pattern string, handler http.Handler)
- func MountPoint(mountPoint string, next http.Handler) http.Handler
- func PathParams(ctx context.Context) map[string]string
- func RESTOwnershipCheck(ctx context.Context, entity any) bool
- func RegisterRouteInformer[T http.Handler](fn func(v T) RouteInfo) func()
- func WithAccessLog(next http.Handler) http.Handler
- func WithMiddleware(handler http.Handler, ffns ...MiddlewareFactoryFunc) http.Handler
- func WithPathParam(ctx context.Context, key, val string) context.Context
- func WithRoundTripper(transport http.RoundTripper, rts ...RoundTripperFactoryFunc) http.RoundTripper
- type AccessLog
- type ClientErrUnexpectedResponse
- type ErrorHandler
- type FormURLEncodedCodec
- type IDContextKey
- type IDConverter
- type IDInContext
- type IntID
- type Mapper
- type MediaTypeCodecs
- type MediaTypeMappings
- type MiddlewareFactoryFunc
- type Multiplexer
- type PathInfo
- type RESTClient
- func (r RESTClient[ENT, ID]) Create(ctx context.Context, ptr *ENT) error
- func (r RESTClient[ENT, ID]) DeleteAll(ctx context.Context) error
- func (r RESTClient[ENT, ID]) DeleteByID(ctx context.Context, id ID) error
- func (r RESTClient[ENT, ID]) FindAll(ctx context.Context) (iter.Seq2[ENT, error], error)
- func (r RESTClient[ENT, ID]) FindByID(ctx context.Context, id ID) (ent ENT, found bool, err error)
- func (r RESTClient[ENT, ID]) FindByIDs(ctx context.Context, ids ...ID) (iter.Seq2[ENT, error], error)
- func (r RESTClient[ENT, ID]) Update(ctx context.Context, ptr *ENT) error
- type RESTHandler
- type RestClientCodec
- type RetryRoundTripper
- type RoundTripperFactoryFunc
- type RoundTripperFunc
- type RouteInfo
- type RouteInformer
- type Router
- func (router *Router) Connect(path string, handler http.Handler)
- func (router *Router) Delete(path string, handler http.Handler)
- func (router *Router) Get(path string, handler http.Handler)
- func (router *Router) Handle(pattern string, handler http.Handler)
- func (router *Router) Head(path string, handler http.Handler)
- func (router *Router) Mount(path string, handler http.Handler)
- func (router *Router) Namespace(path string, blk func(ro *Router))
- func (router *Router) On(method, path string, handler http.Handler)
- func (router *Router) Options(path string, handler http.Handler)
- func (router *Router) Patch(path string, handler http.Handler)
- func (router *Router) Post(path string, handler http.Handler)
- func (router *Router) Put(path string, handler http.Handler)
- func (router *Router) Resource(identifier string, h restHandler)
- func (router *Router) RouteInfo() RouteInfo
- func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (router *Router) Sub(path string) *Router
- func (router *Router) Trace(path string, handler http.Handler)
- func (router *Router) Use(mws ...MiddlewareFactoryFunc)
- type StringID
Examples ¶
- GetRouteInfo (HttpServeMux)
- GetRouteInfo (HttpkitRouter)
- Mount
- RESTClient
- RESTClient (Subresource)
- RESTHandler
- RESTHandler (WithIndexFilteringByQuery)
- RESTHandler (WithMediaTypeConfiguration)
- RetryRoundTripper
- Router
- Router.Connect
- Router.Delete
- Router.Get
- Router.Handle
- Router.Head
- Router.Namespace
- Router.Patch
- Router.Post
- Router.Put
- WithAccessLog
- WithRoundTripper
Constants ¶
This section is empty.
Variables ¶
var DefaultBodyReadLimit int = 16 * iokit.Megabyte
DefaultBodyReadLimit is the maximum number of bytes that a httpkit.Handler will read from the requester, if the Handler.BodyReadLimit is not provided.
var DefaultRestClientHTTPClient http.Client = http.Client{ Transport: RetryRoundTripper{ RetryStrategy: resilience.ExponentialBackoff{ Delay: time.Second, Timeout: time.Minute, }, }, Timeout: 25 * time.Second, }
var ErrEntityAlreadyExist = errorkit.UserError{
Code: "entity-already-exists",
Message: "The entity could not be created as it already exists.",
}
var ErrEntityNotFound = errorkit.UserError{
Code: "entity-not-found",
Message: "The requested entity is not found in this resource.",
}
var ErrForbidden = errorkit.UserError{
Code: "forbidden",
Message: "Operation permanently forbidden. Repeating the request will yield the same result.",
}
var ErrInternalServerError = errorkit.UserError{
Code: "internal-server-error",
Message: "An unexpected internal server error occurred.",
}
var ErrInvalidRequestBody = errorkit.UserError{
Code: "invalid-request-body",
Message: "The request body is invalid.",
}
var ErrMalformedID = errorkit.UserError{
Code: "malformed-id-in-path",
Message: "The received entity id in the path is malformed.",
}
var ErrMethodNotAllowed = errorkit.UserError{
Code: "rest-method-not-allowed",
Message: "The requested RESTful method is not supported.",
}
var ErrPathNotFound = errorkit.UserError{
Code: "path-not-found",
Message: "The requested path is not found.",
}
var ErrRequestEntityTooLarge = errorkit.UserError{
Code: "request-entity-too-large",
Message: "The request body was larger than the size limit allowed for the server.",
}
var ErrResponseEntityTooLarge = errorkit.UserError{
Code: "response-entity-too-large",
Message: "The response body was larger than the size limit allowed for the client.",
}
Functions ¶
func ErrorMapping ¶ added in v0.220.0
func IsSuccess ¶ added in v0.290.0
IsSuccess: Success - The action was successfully received, understood, and accepted
func LookupRequest ¶ added in v0.233.0
LookupRequest will check if the context contains an http request. LookupRequest is mostly meant to be used from functions like Index in RestResource.
func Mount ¶ added in v0.187.0
func Mount(mux Multiplexer, pattern string, handler http.Handler)
Mount will help to register a handler on a request multiplexer in both as the concrete path to the handler and as a prefix match. example:
if pattern -> "/something" registered as "/something" for exact match registered as "/something/" for prefix match
Example ¶
package main import ( "net/http" "go.llib.dev/frameless/pkg/httpkit" ) func main() { var ( apiV0 http.Handler webUI http.Handler mux = http.NewServeMux() ) httpkit.Mount(mux, "/api/v0", apiV0) httpkit.Mount(mux, "/ui", webUI) }
Output:
func MountPoint ¶ added in v0.220.0
func RESTOwnershipCheck ¶ added in v0.261.0
RESTOwnershipCheck
RESTOwnershipCheck checks if an entity (e.g., a note, attachment) belongs to the current REST request scope by verifying its association with the specified parent resource. This function ensures data isolation, allowing only relevant entities to be accessed or modified.
Key Concept ¶
In nested REST resources, RESTOwnershipCheck enforces that an entity is linked to its parent resource in the current request, helping maintain secure and isolated data access.
How It Works ¶
- Identify Parent and Entity:
- For a request path like /users/1/notes, 1 represents the parent resource (user), and notes are entities associated with that user.
- Verify Association:
- The function checks if the entity’s foreign key (e.g., UserID in a note) matches the parent resource ID in the request.
- Return Outcome:
- True if the entity is correctly associated (e.g., the note is owned by user 1).
- False if it isn’t, blocking access to unrelated data.
Examples ¶
- Basic: For /users/1/notes, RESTOwnershipCheck ensures each note is owned by user 1. - Nested: For /users/1/notes/2/attachments, it confirms that an attachment belongs to note 2 under user 1.
This check maintains ownership boundaries and enhances security in nested REST resources.
func RegisterRouteInformer ¶ added in v0.273.0
func WithAccessLog ¶ added in v0.271.0
WithAccessLog is a MiddlewareFactoryFunc for adding AccessLog to a http.Handler middleware stack.
Example ¶
package main import ( "net/http" "go.llib.dev/frameless/pkg/httpkit" ) func main() { var h http.Handler = httpkit.NewRouter() h = httpkit.WithMiddleware(h, httpkit.WithAccessLog, /* plus other middlewares */) // or h = httpkit.WithAccessLog(h) }
Output:
func WithMiddleware ¶ added in v0.211.0
func WithMiddleware(handler http.Handler, ffns ...MiddlewareFactoryFunc) http.Handler
WithMiddleware will combine an http.Handler with a stack of middleware factory functions. The order in which you pass the MiddlewareFactoryFunc -s is the same as the order, they will be called during the http.Handler.ServeHTTP method call.
func WithPathParam ¶ added in v0.220.0
func WithRoundTripper ¶ added in v0.216.0
func WithRoundTripper(transport http.RoundTripper, rts ...RoundTripperFactoryFunc) http.RoundTripper
WithRoundTripper will combine an http.RoundTripper with a stack of middleware factory functions. The execution order is in which you pass the factory funcs.
Example ¶
package main import ( "net/http" "go.llib.dev/frameless/pkg/httpkit" ) func main() { transport := httpkit.WithRoundTripper(nil, func(next http.RoundTripper) http.RoundTripper { return httpkit.RoundTripperFunc(func(request *http.Request) (*http.Response, error) { request.Header.Set("Authorization", "<type> <credentials>") return next.RoundTrip(request) }) }) _ = &http.Client{ Transport: transport, } }
Output:
Types ¶
type AccessLog ¶
type ClientErrUnexpectedResponse ¶ added in v0.220.0
type ClientErrUnexpectedResponse struct { StatusCode int Body string URL *url.URL RequestMethod string RequestURL *url.URL }
func (ClientErrUnexpectedResponse) Error ¶ added in v0.220.0
func (err ClientErrUnexpectedResponse) Error() string
type ErrorHandler ¶ added in v0.220.0
type ErrorHandler interface {
HandleError(w http.ResponseWriter, r *http.Request, err error)
}
type FormURLEncodedCodec ¶ added in v0.231.0
type FormURLEncodedCodec struct{}
type IDContextKey ¶ added in v0.261.0
type IDContextKey[ENT, ID any] struct{}
type IDConverter ¶ added in v0.220.0
IDConverter is an OldMapping tool that you can embed in your OldMapping implementation, and it will implement the ID encoding that will be used in the URL.
func (IDConverter[ID]) FormatID ¶ added in v0.220.0
func (m IDConverter[ID]) FormatID(id ID) (string, error)
func (IDConverter[ID]) ParseID ¶ added in v0.220.0
func (m IDConverter[ID]) ParseID(data string) (ID, error)
type IDInContext ¶ added in v0.220.0
type IDInContext[CtxKey, EntityIDType any] struct{}
IDInContext is an OldMapping tool that you can embed in your OldMapping implementation, and it will implement the context handling related methods.
func (IDInContext[CtxKey, EntityIDType]) ContextLookupID ¶ added in v0.220.0
func (cm IDInContext[CtxKey, EntityIDType]) ContextLookupID(ctx context.Context) (EntityIDType, bool)
func (IDInContext[CtxKey, EntityIDType]) ContextWithID ¶ added in v0.220.0
func (cm IDInContext[CtxKey, EntityIDType]) ContextWithID(ctx context.Context, id EntityIDType) context.Context
type IntID ¶ added in v0.220.0
type IntID[ID ~int] struct{}
IntID is an OldMapping tool that you can embed in your OldMapping implementation, and it will implement the ID encoding that will be used in the URL.
type Mapper ¶ added in v0.230.0
type Mapper[ENT any] interface { // contains filtered or unexported methods }
Mapper is a generic interface used for representing a DTO-ENT mapping relationship. Its primary function is to allow Resource to list various mappings, each with its own DTO type, for different MIMEType values. This means we can use different DTO types within the same restful Resource handler based on different content types, making it more flexible and adaptable to support different Serialization formats.
It is implemented by DTOMapping.
type MediaTypeCodecs ¶ added in v0.259.0
type MediaTypeMappings ¶ added in v0.259.0
type MiddlewareFactoryFunc ¶ added in v0.211.0
MiddlewareFactoryFunc is a constructor function that is meant to wrap an http.Handler with given middleware. Its http.Handler argument represents the next middleware http.Handler in the pipeline.
type Multiplexer ¶ added in v0.211.0
Multiplexer represents a http request Multiplexer.
type RESTClient ¶ added in v0.259.0
type RESTClient[ENT, ID any] struct { // BaseURL [required] is the url base that the rest client will use to access the remote resource. BaseURL string // HTTPClient [optional] will be used to make the http requests from the rest client. // // default: httpkit.DefaultRestClientHTTPClient HTTPClient *http.Client // MediaType [optional] is used in the related headers such as Content-Type and Accept. // // default: httpkit.DefaultCodec.MediaType MediaType mediatype.MediaType // Mapping [optional] is used if the ENT must be mapped into a DTO type prior to serialization. // // default: ENT type is used as the DTO type. Mapping dtokit.Mapper[ENT] // Codec [optional] is used for the serialization process with DTO values. // // default: DefaultCodecs will be used to find a matching codec for the given media type. Codec codec.Codec // MediaTypeCodecs [optional] is a registry that helps choose the right codec for each media type. // // default: DefaultCodecs MediaTypeCodecs MediaTypeCodecs // IDFormatter [optional] is used to format the ID value into a string format that can be part of the request path. // // default: httpkit.IDFormatter[ID].Format IDFormatter func(ID) (string, error) // IDA [optional] is the ENT's ID accessor helper, to describe how to look up the ID field in a ENT. // // default: extid.Lookup[ID, ENT] IDA extid.Accessor[ENT, ID] // WithContext [optional] allows you to add data to the context for requests. // If you need to select a RESTful subresource and return it as a RestClient, // you can use this function to add the selected resource's path parameter // to the context using httpkit.WithPathParam. // // default: ignored WithContext func(context.Context) context.Context // PrefetchLimit is used when a methor requires fetching entities ahead. // If set to -1, then prefetch is disabled. // // default: 20 PrefetchLimit int // BodyReadLimit is the read limit in bytes of how much response body is accepted from the server. // When set to -1, it accepts indifinitelly. // // default: DefaultBodyReadLimit BodyReadLimit int // DisableStreaming switches off the streaming behaviour in results processing, // meaning the entire response body of the RESTful API is loaded at once, rather than bit by bit. // By enabling DisableStreaming, you load everything into memory upfront and can close the response connection faster. // // However, this increases memory usage and stops you from handling a very long JSON stream. // In return, it could reduce the number of open connections, helping ease the server’s load. // // This is useful for situations: // - where slowwer servers might feel overwhelmed with holding connections concurrently open (lik ruby's unicorn server) // - when the server incorrect mistake the streaming based request processing as a slow-client attack. // // default: false DisableStreaming bool }
Example ¶
package main import ( "context" "go.llib.dev/frameless/pkg/dtokit" "go.llib.dev/frameless/pkg/httpkit" "go.llib.dev/frameless/pkg/httpkit/mediatype" "go.llib.dev/frameless/pkg/jsonkit" "go.llib.dev/frameless/spechelper/testent" ) func main() { var ( ctx = context.Background() fooRepo = httpkit.RESTClient[testent.Foo, testent.FooID]{ BaseURL: "https://mydomain.dev/api/v1/foos", MediaType: mediatype.JSON, Mapping: dtokit.Mapping[testent.Foo, testent.FooDTO]{}, Codec: jsonkit.Codec{}, // leave IDFormatter empty for using the default id formatter, or provide your own IDFormatter: func(fi testent.FooID) (string, error) { return httpkit.IDConverter[testent.FooID]{}.Format(fi) }, IDA: func(f *testent.Foo) *testent.FooID { return &f.ID }, } ) var ent = testent.Foo{ Foo: "foo", Bar: "bar", Baz: "baz", } err := fooRepo.Create(ctx, &ent) if err != nil { panic(err) } gotEnt, found, err := fooRepo.FindByID(ctx, ent.ID) if err != nil { panic(err) } _, _ = gotEnt, found err = fooRepo.Update(ctx, &ent) if err != nil { panic(err) } err = fooRepo.DeleteByID(ctx, ent.ID) if err != nil { panic(err) } }
Output:
Example (Subresource) ¶
package main import ( "context" "go.llib.dev/frameless/pkg/httpkit" "go.llib.dev/frameless/spechelper/testent" ) func main() { barResourceClient := httpkit.RESTClient[testent.Bar, testent.BarID]{ BaseURL: "https://example.com/foos/:foo_id/bars", WithContext: func(ctx context.Context) context.Context { // here we define that this barResourceClient is the subresource of a Foo value (id=fooidvalue) return httpkit.WithPathParam(ctx, "foo_id", "fooidvalue") }, } ctx := context.Background() _, _ = barResourceClient.FindAll(ctx) _, _, _ = barResourceClient.FindByID(ctx, "baridvalue") }
Output:
func (RESTClient[ENT, ID]) Create ¶ added in v0.259.0
func (r RESTClient[ENT, ID]) Create(ctx context.Context, ptr *ENT) error
func (RESTClient[ENT, ID]) DeleteAll ¶ added in v0.259.0
func (r RESTClient[ENT, ID]) DeleteAll(ctx context.Context) error
func (RESTClient[ENT, ID]) DeleteByID ¶ added in v0.259.0
func (r RESTClient[ENT, ID]) DeleteByID(ctx context.Context, id ID) error
func (RESTClient[ENT, ID]) FindByID ¶ added in v0.259.0
func (r RESTClient[ENT, ID]) FindByID(ctx context.Context, id ID) (ent ENT, found bool, err error)
type RESTHandler ¶ added in v0.259.0
type RESTHandler[ENT, ID any] struct { // Create will create a new entity in the restful resource. // Create is a collection endpoint. // POST / Create func(ctx context.Context, ptr *ENT) error // Index will return the entities, optionally filtered with the query argument. // Index is a collection endpoint. // GET / Index func(ctx context.Context) (iter.Seq2[ENT, error], error) // Show will return a single entity, looked up by its ID. // Show is a resource endpoint. // GET /:id Show func(ctx context.Context, id ID) (ent ENT, found bool, err error) // Update will update/replace an entity with the new state. // Update is a resource endpoint. // PUT /:id - update/replace // PATCH /:id - partial update (WIP) Update func(ctx context.Context, ptr *ENT) error // Destroy will delete an entity, identified by its id. // Destroy is a resource endpoint. // Delete /:id Destroy func(ctx context.Context, id ID) error // DestroyAll will delete all entity. // DestroyAll is a collection endpoint // Delete / DestroyAll func(ctx context.Context) error // ResourceRoutes field is an http.Handler that will receive resource-specific requests. // ResourceRoutes field is optional. // ResourceRoutes are resource endpoints. // // The http.Request.Context will contain the parsed ID from the request path, // and can be accessed with the IDContextKey. // // Example paths // /plural-resource-identifier-name/:id/sub-routes // /users/42/status // /users/42/jobs/13 // // Request paths will be stripped from their prefix. // For example, "/users/42/jobs" will end up as "/jobs". ResourceRoutes http.Handler // Mapping [optional] is the generic ENT to DTO mapping configuration. // // default: the ENT type itself is used as the DTO type. Mapping dtokit.Mapper[ENT] // MediaType [optional] configures what MediaType the handler should use, when the request doesn't defines it. // // default: DefaultCodec.MediaType MediaType mediatype.MediaType // MediaTypeMappings [optional] defines a per MediaType DTO Mapping, // that takes priority over the Mapping. // // default: Mapping is used. MediaTypeMappings MediaTypeMappings[ENT] // MediaTypeCodecs [optional] contains per media type related codec which is used to marshal and unmarshal data in the response and response body. // // default: will use httpkit.DefaultCodecs MediaTypeCodecs MediaTypeCodecs // ErrorHandler [optional] is used to handle errors from the request, by mapping the error value into an error DTO Mapping. ErrorHandler ErrorHandler // IDContextKey is an optional field used to store the parsed ID from the URL in the context. // // default: IDContextKey[ENT, ID]{} IDContextKey any // IDParser [optional] is the ID converter which is used to parse the ID value from the request path. // // default: IDConverter[ID]{}.ParseID(rawID) IDParser func(string) (ID, error) // IDAccessor [optional] tells how to look up or set the ENT's ID. // // Default: extid.Lookup / extid.Set IDAccessor extid.Accessor[ENT, ID] // BodyReadLimit is the max bytes that the handler is willing to read from the request body. // If BodyReadLimit set to -1, then body io reading is not limited. // // default: DefaultBodyReadLimit, which is preset to 16MB. BodyReadLimit iokit.ByteSize // CollectionContext is called when a collection endpoint is called. // // applies to: // - CREATE // - INDEX CollectionContext func(context.Context) (context.Context, error) // ResourceContext is called when a resource endpoint is called. // // applies to: // - SHOW // - UPDATE // - DESTORY // - sub routes ResourceContext func(context.Context, ID) (context.Context, error) // Filters [optional] // // Filters allow the definition of client side requested server side response filtering. // Such as limiting the results of the Index endpoint by query parameters. // This approach enables efficient retrieval of specific subsets of resources // without requiring the client to fetch and process the entire collection. Filters []func(context.Context, ENT) bool // ScopeAware flags the RESTHandler that is is aware of the REST scope, such as being a nested resource. // > RESTHandler[Note, NoteID] that is a subresource of a RESTHandler[User, UserID] // > /users/:user_id/notes -> accessed notes should belong to a given :user_id only. // // If the current handler is not ScopeAware, then we assume so does its REST methods, // and when the RESTHandler used as a subresource ("/users/:user_id/notes"), // to avoid unwanted consequences, the DestroyAll operation will be either disabled or replaced with a sequenced of deletion using a scoped id list. // // Additionally, the Destroy operation is also disabled // unless the Show command is used to retrieve the ENT for validation with Constraint. ScopeAware bool // DisableOwnershipConstraint will disable the ownership check when the RESTHandler is in a sub-resource scope. DisableOwnershipConstraint bool // CommitManager [WIP] // // CommitManager is meant to make the API interaction transactional. CommitManager comproto.OnePhaseCommitProtocol }
RESTHandler implements an http.Handler that adheres to the Representational State of Resource (REST) architectural style.
## What is REST?
REST, short for Representational State Transfer, is an architectural style for designing networked applications. It was introduced by Roy Fielding in his 2000 PhD dissertation.
The primary goals of a RESTful API are to: * Provide a uniform interface for interacting with resources (CRUD over HTTP) * Separate concerns between client and server * Use standard HTTP methods (e.g., GET, POST, PUT, DELETE) to manipulate resources
## Automatic Resource Relationships
One of the key features of our RESTHandler is its ability to automatically infer relationships between resources. This means that when you define a nested URL structure, such as `/users/:user_id/notes/:note_id/attachments/:attachment_id`, our handler will automatically associate the corresponding entities and persist their relationships.
For example, if we have three entities: User, Note, and Attachment, where:
* A Note belongs to a User (identified by `Note#UserID`) * A Note has many Attachments (identified by `Note#Attachments`)
When someone creates a new Note, our handler will automatically infer the UserID from the URL parameter `:user_id`. Similarly, when accessing the path `/users/:user_id/notes`, our handler will return only the notes that are scoped to the specified user.
## Ownership Constraints
But what happens if you want to restrict access to certain resources based on their relationships? That's where ownership constraints come in. When you make a controller "not aware of the REST scope", our handler will apply an ownership constraint, which limits sub-resources to only those that are owned by the parent resource.
To illustrate this, let's say we have the same entities as before: User, Note, and Attachment. If someone tries to access `/users/:user_id/notes`, they will only see notes that belong to the specified user. If they try to create a new note with an invalid or missing `:user_id` parameter, our handler will prevent the creation of the note.
This feature helps ensure data consistency and security by enforcing relationships between resources.
## Conclusion
Our RESTHandler provides a solid foundation for building RESTful APIs that meet the primary goals of REST. With its automatic resource relationship inference and ownership constraint features, you can focus on building robust and scalable applications with ease.
Example ¶
fooRepository := memory.NewRepository[X, XID](memory.NewMemory()) fooRestfulResource := httpkit.RESTHandler[X, XID]{ Create: fooRepository.Create, Index: fooRepository.FindAll, Show: fooRepository.FindByID, Update: fooRepository.Update, Destroy: fooRepository.DeleteByID, DestroyAll: fooRepository.DeleteAll, } mux := http.NewServeMux() httpkit.Mount(mux, "/foos", fooRestfulResource)
Output:
Example (WithIndexFilteringByQuery) ¶
fooRepository := memory.NewRepository[X, XID](memory.NewMemory()) fooRestfulResource := httpkit.RESTHandler[X, XID]{ Index: func(ctx context.Context) (iter.Seq2[X, error], error) { foos, err := fooRepository.FindAll(ctx) if err != nil { return foos, err } req, _ := httpkit.LookupRequest(ctx) if bt := req.URL.Query().Get("bigger"); bt != "" { bigger, err := strconv.Atoi(bt) if err != nil { return nil, err } foos = iterkit.OnErrSeqValue(foos, func(itr iter.Seq[X]) iter.Seq[X] { return iterkit.Filter(itr, func(foo X) bool { return bigger < foo.N }) }) } return foos, nil }, } mux := http.NewServeMux() httpkit.Mount(mux, "/foos", fooRestfulResource)
Output:
Example (WithMediaTypeConfiguration) ¶
fooRepository := memory.NewRepository[X, XID](memory.NewMemory()) fooRestfulResource := httpkit.RESTHandler[X, XID]{ Create: fooRepository.Create, Index: fooRepository.FindAll, Show: fooRepository.FindByID, Update: fooRepository.Update, Destroy: fooRepository.DeleteByID, Mapping: dtokit.Mapping[X, XDTO]{}, MediaType: mediatype.JSON, // we can set the preferred default media type in case the requester don't specify it. MediaTypeMappings: httpkit.MediaTypeMappings[X]{ // we can populate this with any media type we want mediatype.JSON: dtokit.Mapping[X, XDTO]{}, }, MediaTypeCodecs: httpkit.MediaTypeCodecs{ // we can populate with any custom codec for any custom media type mediatype.JSON: jsonkit.Codec{}, }, } mux := http.NewServeMux() httpkit.Mount(mux, "/foos", fooRestfulResource)
Output:
func RESTHandlerFromCRUD ¶ added in v0.261.0
func RESTHandlerFromCRUD[ENT, ID any](repo crud.ByIDFinder[ENT, ID], conf ...func(h *RESTHandler[ENT, ID])) RESTHandler[ENT, ID]
func (RESTHandler[ENT, ID]) RouteInfo ¶ added in v0.273.0
func (h RESTHandler[ENT, ID]) RouteInfo() RouteInfo
func (RESTHandler[ENT, ID]) ServeHTTP ¶ added in v0.259.0
func (h RESTHandler[ENT, ID]) ServeHTTP(w http.ResponseWriter, r *http.Request)
type RestClientCodec ¶ added in v0.259.0
type RestClientCodec interface { codec.Codec codec.ListDecoderMaker }
type RetryRoundTripper ¶
type RetryRoundTripper struct { // Transport specifies the mechanism by which individual // HTTP requests are made. // // Default: http.DefaultTransport Transport http.RoundTripper // RetryStrategy will be used to evaluate if a new retry attempt should be done. // // Default: retry.ExponentialBackoff RetryStrategy resilience.RetryPolicy[resilience.FailureCount] // OnStatus is an [OPTIONAL] configuration field that could contain whether a certain http status code should be retried or not. // The RetryRoundTripper has a default behaviour about which status code can be retried, and this option can override that. OnStatus map[int]bool }
Example ¶
package main import ( "net/http" "time" "go.llib.dev/frameless/pkg/httpkit" "go.llib.dev/frameless/pkg/resilience" ) func main() { httpClient := http.Client{ Transport: httpkit.RetryRoundTripper{ RetryStrategy: resilience.ExponentialBackoff{ // optional Timeout: 5 * time.Minute, }, Transport: http.DefaultTransport, // optional OnStatus: map[int]bool{ // optional http.StatusTeapot: true, http.StatusTooManyRequests: false, }, }, } httpClient.Get("https://go.llib.dev") }
Output:
type RoundTripperFactoryFunc ¶ added in v0.216.0
type RoundTripperFactoryFunc func(next http.RoundTripper) http.RoundTripper
RoundTripperFactoryFunc is a constructor function that is meant to wrap an http.RoundTripper with given middleware. Its http.RoundTripper argument represents the next middleware http.RoundTripper in the pipeline.
type RoundTripperFunc ¶
type RouteInfo ¶ added in v0.273.0
type RouteInfo []PathInfo
func GetRouteInfo ¶ added in v0.273.0
Example (HttpServeMux) ¶
mux := http.NewServeMux() mux.Handle("/foo", nullHandler) mux.Handle("/bar/", nullHandler) _ = httpkit.GetRouteInfo(mux) // ALL /foo // ALL /bar/
Output:
Example (HttpkitRouter) ¶
var ro httpkit.Router ro.Get("/test", nullHandler) mux := http.NewServeMux() mux.Handle("/foo", nullHandler) mux.Handle("/bar/", nullHandler) ro.Mount("/mux", mux) _ = httpkit.GetRouteInfo(mux) // GET /test // ALL /foo // ALL /bar/
Output:
func (RouteInfo) WithMountPoint ¶ added in v0.273.0
type RouteInformer ¶ added in v0.273.0
type RouteInformer interface {
RouteInfo() RouteInfo
}
type Router ¶ added in v0.220.0
type Router struct {
// contains filtered or unexported fields
}
Example ¶
var router httpkit.Router router.Namespace("/path", func(r *httpkit.Router) { r.Use(SampleMiddleware) r.Get("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })) r.Post("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })) r.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // sub route catch-all handle })) r.Resource("foo", httpkit.RESTHandler[Foo, FooID]{ Mapping: dtokit.Mapping[Foo, FooDTO]{}, Index: func(ctx context.Context) (iter.Seq2[Foo, error], error) { foo := Foo{ ID: "42", Foo: "foo", Bar: "bar", Baz: "baz", } return iterkit.ToErrSeq(iterkit.Slice([]Foo{foo})), nil }, }) }) router.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // handler that catches all requests that doesn't match anything directly })) router.Handle("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // /foo endpoint for all methods }))
Output:
func (*Router) Connect ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Connect("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
Output:
func (*Router) Delete ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Delete("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
Output:
func (*Router) Get ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Get("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
Output:
func (*Router) Handle ¶ added in v0.220.0
Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
Example ¶
var router httpkit.Router var handler http.Handler // single endpoint router.Handle("/foo", handler) // catch all endpoint router.Handle("/foo/", handler)
Output:
func (*Router) Head ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Head("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
Output:
func (*Router) Mount ¶ added in v0.220.0
Mount will mount a handler to the router. Mounting a handler will make the path observed as its root point to the handler. TODO: make this true :D
func (*Router) Namespace ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Namespace("/top", func(r *httpkit.Router) { r.Get("/sub", /* /top/sub */ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })) })
Output:
func (*Router) Patch ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Patch("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
Output:
func (*Router) Post ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Post("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
Output:
func (*Router) Put ¶ added in v0.220.0
Example ¶
var router httpkit.Router router.Put("/foo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) }))
Output:
func (*Router) Resource ¶ added in v0.220.0
Resource will register a restful resource path using the Resource handler.
Paths for Router.Resource("/users", httpkit.RESTHandler[User, UserID]):
INDEX - GET /users CREATE - POST /users SHOW - GET /users/:id UPDATE - PUT /users/:id DESTROY - DELETE /users/:id
func (*Router) ServeHTTP ¶ added in v0.220.0
func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request)
func (*Router) Use ¶ added in v0.220.0
func (router *Router) Use(mws ...MiddlewareFactoryFunc)
Use will instruct the router to use a given MiddlewareFactoryFunc to