Giter Site home page Giter Site logo

pocket's Introduction

Pocket Gopher!

Pocket gophers, commonly referred to as just gophers, are burrowing rodents of the family Geomyidae.

Actions Status

(Work In Progress)

Pocket is a neat little web library to help you write cleaner HTTP request handlers!

It's inspired by how a few other languages provide handler APIs, mainly Rocket (hence the name!)

Example

Here's a quick example:

func handler(props struct {
    UserID   string `param:"user_id"`
    BodyJSON J
}) pocket.Responder {
    // Do stuff with `props`!
    // simply return an error or a responder! no more writing to w!
}

Why?

Go has a great ecosystem for HTTP middleware thanks to the standard Request/ResponseWriter interface. This pattern is great, until it comes to writing your actual business logic handlers. The Request/ResponseWriter is just too low level for doing quick things like getting query parameters or grabbing some JSON.

Sure, you can write some helpers for this but then if you have a few projects, you end up copying/importing and calling this boilerplate all over the place. Doing it declaratively is much nicer!

Note: This package makes generous use of reflection and, while all efforts are made to cache unnecessary reflection calls and perform most of at handler generation-time, this reflection may impact extremely high-traffic web servers. There are currently no benchmarks but this is something I plan to work on in future.

Some Ideas

A scratchpad for ideas for this library!

  • func(string)/func() string signatures for quick and easy text requests/responses.
  • func(T)/func(T) signatures for quick JSON endpoints.
  • nice default for func(...) error signatures
  • Provide some configuration for the handler generator:
    • mapping from error types to statuses, for those func(...) error sigs.
    • wrap JSON responses in some default body
  • generate index/sitemap/documentation based on signatures

pocket's People

Contributors

southclaws avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

isgasho

pocket's Issues

Expose a return type or just use `error`?

Hit a decision point with some interesting arguments.

Context: Handlers will behave differently depending on their return type. Currently, handlers can have three return signatures:

  • error
  • pocket.Responder
  • None
    An error return type means the handler can return either a "200 OK" or a "500 Internal Server Error" with the .Error() written to the response body. A pocket.Responder is an interface that declares some methods for constructing an arbitrary response body. The library includes some basics but a user is free to write their own types that satisfy this interface.

The decision is to continue with the pocket.Responder interface. This mainly comes to performance (not benchmarked any of this yet).

The case for pocket.Responder and error and no return

This means the handler generator can know beforehand whether a function returns a responder or just an error. This means the generator can set the returns flag on the handler and then at runtime, the handler simply checks that in order to know what to expect. It grabs the return value, coerces it to a pocket.Responder and calls the necessary methods on it to construct a response.

This means minimal reflection at runtime since the type assertion is done at generation time.

The case for error only

If handler signatures could only ever return an error this simplifies the generator code and also means a user only has to remember a single simple return type and focus on their props.

The case for no return can also be removed since an error can just be nil.

The downside here is the generator has no way of knowing what type is being returned so at runtime, the return value must be .Convert'd then .Interface()'d then type-switched to determine if it's a plain error or a pocket.Responder then generate a response accordingly.

Another downside is the fact that successful responses that contain data aren't really "errors".


Might be over thinking but thought it was worth raising for discussion. For now, pocket.Responder is exported and checked for when the user wants to use more specific responses (instead of just 200 adn 500 with error).

Gorilla/mux integration

One of the neat things Rocket.rs does is use function attributes to both declare the route that will bind to a function and specify the variables within a route. See an example of that here: https://rocket.rs/v0.4/guide/requests/#dynamic-paths

This is unfortunately not possible to carbon-copy due to Go's simplicity and minimalism. Also, routing/multiplexing isn't the goal of this library because there are plenty of great libraries for these tasks out there already!

I can however imagine building integrations for popular routers.

Gorilla's Mux library registers routes like this:

r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)

That is, the route goes first then the handler. Well, Go will slot the return values of a function into the arguments of another if they are an exact match. So, if Pocket provides a function that returns (string, func(http.ResponseWriter, *http.Request)) then Pocket can have access to the exact route declaration that is passed to Gorilla! Neat!

It would look something like this:

r.HandleFunc(pocket.HandlerGorilla(
    "/articles/{category}/{sort:(?:asc|desc|new)}",
    ArticlesCategoryHandler,
))

Where ArticlesCategoryHandler is a function that has all the magical benefits of Pocket!

So, what would pocket.HandlerGorilla do? It would be something along these lines:

func HandlerGorilla(path string, f interface{}) (
    string,
    func(http.ResponseWriter, *http.Request),
) {
    handler := GenerateHandler(f)

    // parse `path`
    // extract route variables that Gorilla will process - borrow Gorilla code?
    // match these route variables with props in handler.propsT/V
    
    return path, handlerFunc.Execute
}

And on the user side, it would look something like this:

r.HandleFunc(pocket.HandlerGorilla(
	"/articles/{category}/{sort:(?:asc|desc|new)}",
	func(props struct{
		Category string  `route:"category"`
		Sort     SortDir `route:"sort"`
	}) pocket.Responder {
                // perform some business logic with a database using props
		response, err := getData(props.Category, props.Sort)
		if err != nil {
			return pocket.ErrInternalServerError(err)
		}
                // response is some type that satisfies pocket.Responder
		return response
	})
))

(Rough concept, stuff might change)

Here, the props type specifies route tags which map to the {} variables in the route.

Plugins and stuff

While playing with #2 I realised I'm going to have to write some kind of generic way to declare:

  1. things that contribute to props structs (via tags) at build-time
  2. things that hydrate props at request-time

I'm also going to attempt to name these two concepts to make discussion, implementation and documentation easier!

  1. shall be called "Providers", this will probably come with a Provider interface at some point.
  2. shall be called "Extractors" because they extract information from requests!

A provider will be registered with the library (probably some singleton or something) and that provider will inform Pocket of a struct tag and a function to call when that struct tag is encountered in props. That function may return arbitrary metadata related to that field which can be recalled at request-time.

A provider must also supply an extractor which will get access to the incoming request and have the ability to write to the props reflect.Value field that it owns.

After this, I may implement the existing features as "plugins" to test the thing out.

Field names or tags?

I feel like the answer should be tags, but using field name prefixes certainly makes for 1. faster to type 2. cleaner structs...

Field names

func handler(props struct {
    ParamUserID string
    BodyJSON    J
}) error

Struct tags

func handler(props struct {
    UserID string `from:"query" param:"userid"`
    Body   J      `from:"body"  format:"json"`
}) error

Also the Body field - there's only ever going to be one (a request can't have two bodies!) and it's probably always going to be named "Body" or "Payload" or something, so I feel like using the field name to extract it here makes sense... The format probably makes more sense as a tag though.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.