Write HTTP middleware in go language

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

In the process of web development, middleware generally refers to the components of the application that encapsulate the original information and add additional functionality. I don't know why, middleware is often a less popular concept. But I think it's terrific.

First, a good middleware has a single function, pluggable and self-disciplined. This means that you can put it into the application at the interface level and work well. Middleware doesn't affect your code style, it's not a framework, it's just an extra layer of processing your request process. There is no need to rewrite the code: If you want to use a middleware, add it to the app; If you change your mind, just get rid of it. It's so simple.

Look at the Go,http middleware is very popular, the standard library is also the case. Perhaps it doesn't seem obvious that the functions in the Net/http package, such as Stripprefix and Timeouthandler, are the middleware we've defined above: encapsulating the process and adding extra action when processing input or output.

My recent Go Pack nosurf is also a middleware. I have deliberately designed this from the outset. In most cases, you don't have to care about CSRF checks at the application level at all. Nosurf, like other middleware, is very independent and can be used with tools that implement the standard library Net/http interface.

You can also use middleware to do these:
* Mitigate breach attacks with hidden lengths
* Frequency limit
* Block malicious automatic programs
* Provide debugging information
* Add HSTs, x-frame-options head
* Graceful recovery from the anomaly
* and many others.

Write a simple middleware

In the first example, I wrote a middleware that only allows the user to access the server from a specific domain (with domain information in the HTTP host header). Such middleware can protect applications from "host spoofing attacks"

Defining types

For convenience, let's define a type for this middleware, called Singlehost.

type SingleHost struct {    handler     http.Handler    allowedHost string}

Contains only two fields:
* Package of handler. If it is a valid host access, we call this handler.
* Allowed host values.
Since we have the field name lowercase, it makes the field visible only to our own package. We should also write an initialization function.

func NewSingleHost(handler http.Handler, allowedHost string) *SingleHost {    return &SingleHost{handler: handler, allowedHost: allowedHost}}

Processing requests

Now is the actual logic. In order to implement HTTP. Handler, our type Order implements a method:

type Handler interface {        ServeHTTP(ResponseWriter, *Request)}

This is how we implement it:

func (s *SingleHost) ServeHTTP(w http.ResponseWriter, r *http.Request) {    host := r.Host    if host == s.allowedHost {        s.handler.ServeHTTP(w, r)    } else {        w.WriteHeader(403)    }}

The Servehttp function simply checks the host header in the request:

    • If the host header matches the allowedhost of the initialization function setting, the Servehttp method that encapsulates the handler is called.
    • If the host header does not match, it returns a 403 status code (no access).

In the latter case, the Servehttp method that encapsulates handler is not called at all. So the encapsulated handler doesn't have any output at all, and in fact it doesn't even know that there's a request coming.

Now we have finished our own middleware to put it into the application. This time we do not put the handler directly into the Net/http service, but first packaged the handler into the middleware.

singleHosted = NewSingleHost(myHandler, "example.com")http.ListenAndServe(":8080", singleHosted)

A different approach

The middleware we just wrote is simply too simple, just 15 lines of code. In order to write such a middleware, a less general approach is introduced. Because go supports the first type and the closure of functions, and has a concise HTTP. Handlerfunc Wrapper, we can implement it as a simple function instead of writing a separate type. The following is a function-based version of the middleware.

func SingleHost(handler http.Handler, allowedHost string) http.Handler {    ourFunc := func(w http.ResponseWriter, r *http.Request) {        host := r.Host        if host == allowedHost {            handler.ServeHTTP(w, r)        } else {            w.WriteHeader(403)        }    }    return http.HandlerFunc(ourFunc)}

Here we declare a simple function called Singlehost, which accepts a handler and allows the host name. Inside the function, we created a function similar to the previous version of Servehttp. This intrinsic function is actually a closure, so it can be accessed from outside the singlehost. Finally, we use this function as a http.handler through Handlerfunc.

Using handler or defining a Http.handler type is entirely up to you. For a simple case, a function is sufficient. But with the complexity of middleware functionality, you should consider defining your own data structure and putting logic in separate ways.

In fact, both methods of the standard library are used. Stripprefix is a function that returns HANDLERFUNC. Although Timeouthandler is also a function, it returns a custom type that handles requests.

more complicated situations.

Our Singlehost middleware is very simple: check one of the properties of the request first, then either do nothing, pass the request directly to the encapsulated handler, or return a response on your own, without having the encapsulated handler handle the request at all. However, in some cases, not only will trigger some actions based on the request, but also do some finishing work after the encapsulation handler processing, such as modifying the response content.

Easy to add data

If we want to add some data after the contents of the encapsulated handler output, we just need to continue calling write () after the handler is finished:

type AppendMiddleware struct {    handler http.Handler}func (a *AppendMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {    a.handler.ServeHTTP(w, r)    w.Write([]byte("Middleware says hello."))}

The response content should now contain the contents of the encapsulated handler, plus middleware says hello.

Problem is

Doing other response content operation is more troublesome. For example, if we want to write some data before responding to the content. If we call write () before the encapsulated handler, the encapsulated handler is good at losing control of the HTTP status code and HTTP headers. Because the first call to write () outputs the header directly.

To modify the original output (for example, to replace some of these strings), change the specific HTTP headers, and set different status codes are not possible for the same reason: when the encapsulated handler returns, the above data is already sent to the client.

In order to deal with this requirement, we need a special responsewriter that can be used as buffer, which can collect, hold output for modification and so on, and then send it to the client. We can pass this buffer responsewriter to the encapsulated handler instead of the real RW, which avoids sending the data directly to the client.

Fortunately, such a tool does exist in the Go standard library. The Responserecorder in Net/http/httptest is this: it saves the status code, a dictionary that holds the response header, and accumulates the output in buffer. Although it was used for testing (the package name implies this), it is a good fit for our needs.

Let's take a look at an example of using Responserecorder, where everything that responds to the content is modified for a more complete demonstration.

type ModifierMiddleware struct {    handler http.Handler}func (m *ModifierMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {    rec := httptest.NewRecorder()    // passing a ResponseRecorder instead of the original RW    m.handler.ServeHTTP(rec, r)    // after this finishes, we have the response recorded    // and can modify it before copying it to the original RW    // we copy the original headers first    for k, v := range rec.Header() {        w.Header()[k] = v    }    // and set an additional one    w.Header().Set("X-We-Modified-This", "Yup")    // only then the status code, as this call writes out the headers     w.WriteHeader(418)    // the body hasn't been written (to the real RW) yet,    // so we can prepend some data.    w.Write([]byte("Middleware says hello again. "))    // then write out the original body    w.Write(rec.Body.Bytes())}

Here is the output of our packaged handler. Without our middleware encapsulation, the original handler will only output success!.

HTTP/1.1 418 I'm a teapotX-We-Modified-This: YupContent-Type: text/plain; charset=utf-8Content-Length: 37Date: Tue, 03 Sep 2013 18:41:39 GMTMiddleware says hello again. Success!

This approach provides a great convenience. The encapsulated handler is now completely under our control: even after its return, we can manipulate the output in any way.

Share data with other handlers

In different situations, middleware may need to expose specific information to other middleware or applications. For example, Nosurf needs to provide the user with a way to get the CSRF key and the cause of the error (if there is an error).

A suitable model for this requirement is to use a hidden map that will be HTTP. The request pointer points to the data that is needed, and then exposes a package-level (handler level) function to access the data.

I also used this model in Nosurf. Here, I created a global context map. Note that because the go map is not [concurrent access security] (Http://blog.golang.org/go-maps-in-action#TOC_6.) By default, a mutex is required.

type csrfContext struct {    token string    reason error}var (    contextMap = make(map[*http.Request]*csrfContext)    cmMutex    = new(sync.RWMutex))

Use handler to set the data, and then get the data through the exposed function token ().

func Token(req *http.Request) string {    cmMutex.RLock()    defer cmMutex.RUnlock()    ctx, ok := contextMap[req]    if !ok {            return ""    }    return ctx.token}

You can find the complete implementation in the Nosurf code base context.go.

Although I chose to implement this requirement myself in Nosurf, there is actually a handy gorilla/context package that implements a generic map of the Save request information. In most cases, this package is sufficient to meet your needs and prevent you from stepping on the pit when you implement a shared storage. It even has its own middleware that cleans up request information after the request process is over.

Summarize

The purpose of this article is to draw the attention of go users to the concept of middleware and to demonstrate some of the basic components of using go writing middleware. Although go is a relatively young development language, go has a very nice standard HTTP interface. This is one of the reasons why writing middleware with Go is a very simple and even enjoyable process.

However, there is still a lack of high-quality HTTP tools for go. Most of the go middleware ideas I mentioned before have not been implemented. Now that you know how to write middleware with go, why not make one yourself?

PS, you can find all the middleware examples in this article in a GitHub gist.

Original link: Writing HTTP middleware in Go
Reprinted from: Bole Online-codefor

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.