httpin

repository·main·Indexed 18 days ago

https://github.com/ggicci/httpin

A Go library that automates the mapping between HTTP requests and Go structs using struct tags. It supports decoding data from query parameters, headers, form data, JSON/XML bodies, path variables, and file uploads into structs via DecodeTo, Decode[T], or NewInput middleware. It also provides NewRequest to encode Go structs back into http.Request instances.

Tokens
2K
Snippets
9
Records
11
Agent score
14%

What's inside httpin

  1. Overview of httpin

    main

    httpin is a Go library designed to simplify the process of mapping HTTP request data to Go structs and vice versa. It eliminates the need for manual parsing of various HTTP components by using struct tags.

    Key capabilities include decoding data from:

    • Query parameters
    • Headers
    • Form data
    • JSON/XML Body
    • Path variables
    • File uploads

    Key capabilities include encoding data to:

    • Creating http.Request instances from Go structs.
  2. Define HTTP input using `in` struct tags

    main

    To map HTTP request components to a Go struct, use the in struct tag. You can specify multiple sources for a single field by separating them with semicolons. Supported directives include:

    • query=key: Map from a query parameter.
    • header=key: Map from an HTTP header.
    • form=key: Map from form data.
    • path=key: Map from a path variable.
    • default=value: Provide a default value if the key is missing.
    • omitempty: Omit the field if it is empty (useful for encoding).

    Example of a struct defining various input sources:

    type ListUsersInput struct {
    	Token    string  `in:"query=access_token;header=x-access-token"` 
    	Page     int     `in:"query=page;default=1"` 
    	PerPage  int     `in:"query=per_page;default=20"` 
    	IsMember bool    `in:"query=is_member"` 
        Search   *string `in:"query=search;omitempty"` 
    }
  3. Decode an HTTP request into a Go struct

    main

    Once httpin is integrated with your router (e.g., net/http, chi, gorilla/mux, or gin), the decoded input struct is available within the request context. You can retrieve it using r.Context().Value(httpin.Input).

    Note that you must type-assert the value to your specific input struct type.

    func ListUsers(rw http.ResponseWriter, r *http.Request) {
    	// Retrieve the decoded input from the request context
    	input := r.Context().Value(httpin.Input).(*ListUsersInput)
    
    	if input.IsMember {
    		// Do something with the parsed data
    	}
    }
  4. Handle file uploads with the File type

    main

    The File type is used to represent files in multipart/form-data requests.

    • On the server: Use File in your struct to capture uploaded files.
    • On the client: Use File to specify files to be uploaded in a request created via NewRequest.

    Use the following helpers to create File instances:

    • UploadFile(path string): Creates a File from a local file system path.
    • UploadStream(r io.ReadCloser): Creates a File from an io.ReadCloser stream.
    // From local path
    file := httpin.UploadFile("/path/to/file.txt")
    
    // From a stream
    file := httpin.UploadStream(myReader)
  5. Use httpin.NewInput() as middleware

    main

    The recommended way to use httpin in a web server is via NewInput(). This creates an http.Handler middleware that:

    1. Decodes the incoming request into the specified struct type.
    2. If decoding fails, it invokes the configured error handler and terminates the request.
    3. If successful, it injects the decoded struct into the request's context using the httpin.Input key.

    To retrieve the decoded struct in your handler, use r.Context().Value(httpin.Input).(*YourStruct).

    type ListUsersRequest struct {
    	Page    int `in:"query=page,page_index,index"`
    	PerPage int `in:"query=per_page,page_size"`
    }
    
    func ListUsersHandler(rw http.ResponseWriter, r *http.Request) {
    	input := r.Context().Value(httpin.Input).(*ListUsersRequest)
    	// ... use input
    }
    
    func init() {
    	http.Handle("/users", httpin.NewInput(&ListUsersRequest{})(nextHandler))
    }
    type ListUsersRequest struct {
    	Page    int `in:"query=page,page_index,index"`
    	PerPage int `in:"query=per_page,page_size"`
    }
    
    func ListUsersHandler(rw http.ResponseWriter, r *http.Request) {
    	input := r.Context().Value(httpin.Input).(*ListUsersRequest)
    	// ...
    }
    
    func init() {
    	http.Handle("/users", httpin.NewInput(&ListUsersRequest{}).ThenFunc(ListUsersHandler))
    }
  6. Encode a Go struct into an HTTP request

    main

    You can easily generate an http.Request from a Go struct instance using httpin.NewRequest. This is useful for building SDKs or clients where you want to pass a structured payload instead of manually setting query params, headers, and bodies.

    func SDKListUsers() {
    	payload := &ListUsersInput{
    		Token:    os.Getenv("MY_APP_ACCESS_TOKEN"),
    		Page:     2,
    		IsMember: true,
    	}
    
    	// Creates an http.Request from the payload struct
    	req, err := httpin.NewRequest("GET", "/users", payload)
    	if err != nil {
    		// handle error
    	}
    	// ... use req
    }
  7. Configure httpin with Options

    main

    You can customize the behavior of New(), DecodeTo(), Decode[T](), and NewInput() using core.Options.

    Available options (via the Option variable) include:

    • WithErrorHandler: Overrides the default error handler.
    • WithMaxMemory: Overrides the default maximum memory size (32MB) used when reading the request body (relevant for multipart/form-data).
    • WithNestedDirectivesEnabled: Enables or disables nested directives.
  8. Create an HTTP request from a Go struct with NewRequest()

    main

    Use NewRequest (or NewRequestWithContext) to encode a Go struct into an http.Request. The struct's in tags determine how fields are mapped to the request (e.g., query params, headers, body). This is a replacement for http.NewRequest when working with structured data.

    addUserPayload := &AddUserRequest{Name: "John"}
    req, err := httpin.NewRequest("POST", "http://example.com/users", addUserPayload)
    if err != nil {
        // handle error
    }
    http.DefaultClient.Do(req)
    addUserPayload := &AddUserRequest{...}
    addUserRequest, err := httpin.NewRequestWithContext(context.Background(), "GET", "http://example.com", addUserPayload)
    http.DefaultClient.Do(addUserRequest)
  9. Decode an HTTP request to a struct with Decode[T]()

    main

    Use the generic Decode[T] function to decode an http.Request into a new instance of type T. T must be a struct type. It returns a pointer to the populated struct.

    if user, err := httpin.Decode[User](req); err != nil {
        // handle error
    }
    // user is now a *User instance
    if user, err := httpin.Decode[User](req); err != nil {
        // ...
    }
  10. Decode an HTTP request to a struct with DecodeTo()

    main

    Use DecodeTo to populate an existing pointer to a struct with data from an http.Request. The data is extracted from query parameters, headers, form data, JSON/XML payloads, URL path params, and multipart file uploads based on the struct's in tags.

    input := &InputStruct{}
    if err := httpin.DecodeTo(req, input); err != nil {
        // handle error
    }
    // input is now populated
    input := &InputStruct{}
    if err := httpin.DecodeTo(req, input); err != nil { ... }
  11. Retrieve decoded input from Request Context

    main

    When using httpin.NewInput middleware, the decoded struct is stored in the request context. You can access it using the httpin.Input key.

    input := r.Context().Value(httpin.Input).(*InputStruct)