官术网_书友最值得收藏!

Handlers all the way down

For our chat application, we implemented our own http.Handler type (the room) in order to easily compile, execute, and deliver HTML content to browsers. Since this is a very simple but powerful interface, we are going to continue to use it wherever possible when adding functionality to our HTTP processing.

In order to determine whether a user is allowed to proceed, we will create an authorization wrapper handler that will perform the check and pass the execution on to the inner handler only if the user is authorized.

Our wrapper handler will satisfy the same http.Handler interface as the object inside it, allowing us to wrap any valid handler. In fact, even the authentication handler we are about to write could be later encapsulated inside a similar wrapper if required.

Chaining pattern when applied to HTTP handlers

The preceding diagram shows how this pattern could be applied in a more complicated HTTP handler scenario. Each object implements the http.Handler interface. This means that an object could be passed to the http.Handle method to directly handle a request, or it can be given to another object, which could add some kind of extra functionality. The Logging handler may write to a log file before and after the ServeHTTP method is called on the inner handler. Because the inner handler is just another http.Handler, any other handler can be wrapped in (or decorated with) the Logging handler.

It is also common for an object to contain logic that decides which inner handler should be executed. For example, our authentication handler will either pass the execution to the wrapped handler, or handle the request itself by issuing a redirect to the browser.

That's plenty of theory for now; let's write some code. Create a new file called auth.go in the chat folder:

package main 
import ("net/http") 
type authHandler struct { 
  next http.Handler 
} 
func (h *authHandler) ServeHTTP(w http.ResponseWriter, r  *http.Request) { 
  _, err := r.Cookie("auth") 
  if err == http.ErrNoCookie { 
    // not authenticated 
    w.Header().Set("Location", "/login") 
    w.WriteHeader(http.StatusTemporaryRedirect) 
    return 
  }  
  if err != nil { 
    // some other error 
   http.Error(w, err.Error(), http.StatusInternalServerError) 
   return 
  }  
  // success - call the next handler 
  h.next.ServeHTTP(w, r) 
} 
func MustAuth(handler http.Handler) http.Handler { 
  return &authHandler{next: handler} 
} 

The authHandler type not only implements the ServeHTTP method (which satisfies the http.Handler interface), but also stores (wraps) http.Handler in the next field. Our MustAuth helper function simply creates authHandler that wraps any other http.Handler. This is the pattern that allows us to easily add authorization to our code in main.go.

Let's tweak the following root mapping line:

http.Handle("/", &templateHandler{filename: "chat.html"}) 

Let's change the first argument to make it explicit about the page meant for chatting. Next, let's use the MustAuth function to wrap templateHandler for the second argument:

http.Handle("/chat", MustAuth(&templateHandler{filename:  "chat.html"})) 

Wrapping templateHandler with the MustAuth function will cause the execution to run through authHandler first; it will run only to templateHandler if the request is authenticated.

The ServeHTTP method in authHandler will look for a special cookie called auth, and it will use the Header and WriteHeader methods on http.ResponseWriter to redirect the user to a login page if the cookie is missing. Notice that we discard the cookie itself using the underscore character and capture only the returning error; this is because we only care about whether the cookie is present at this point.

Build and run the chat application and try to hit http://localhost:8080/chat:

go build -o chat
./chat -host=":8080"

Tip

You need to delete your cookies to clear out previous authentication tokens or any other cookies that might be left over from other development projects served through the localhost.

If you look in the address bar of your browser, you will notice that you are immediately redirected to the /login page. Since we cannot handle that path yet, you'll just get a 404 page not found error.

主站蜘蛛池模板: 东兰县| 清流县| 岳池县| 新田县| 观塘区| 闵行区| 滨州市| 麻栗坡县| 和平区| 宁城县| 遂川县| 砀山县| 东明县| 漠河县| 巴彦淖尔市| 河源市| 绥阳县| 遂宁市| 德格县| 漳平市| 宁化县| 峨山| 澎湖县| 兰州市| 曲阜市| 邳州市| 厦门市| 朝阳区| 开江县| 建湖县| 墨脱县| 高邮市| 惠州市| 东乌珠穆沁旗| 陵水| 天柱县| 安龙县| 称多县| 克山县| 盐边县| 积石山|