From c02d4af5c6876e99171a9c914be50323436c0de3 Mon Sep 17 00:00:00 2001 From: Dmitry Harnitski Date: Wed, 19 Apr 2017 22:52:26 -0400 Subject: [PATCH 1/3] 3.4 - format code --- en/03.4.md | 235 +++++++++++++++++++++++++++-------------------------- 1 file changed, 118 insertions(+), 117 deletions(-) diff --git a/en/03.4.md b/en/03.4.md index 88fb2d22..45eb19b0 100644 --- a/en/03.4.md +++ b/en/03.4.md @@ -7,13 +7,13 @@ In previous sections, we learned about the work flow of the web and talked a lit Unlike normal HTTP servers, Go uses goroutines for every job initiated by Conn in order to achieve high concurrency and performance, so every job is independent. Go uses the following code to wait for new connections from clients. - - c, err := srv.newConn(rw) - if err != nil { - continue - } - go c.serve() - +```Go +c, err := srv.newConn(rw) +if err != nil { + continue +} +go c.serve() +``` As you can see, it creates a new goroutine for every connection, and passes the handler that is able to read data from the request to the goroutine. ## Customized ServeMux @@ -21,150 +21,151 @@ As you can see, it creates a new goroutine for every connection, and passes the We used Go's default router in previous sections when discussing conn.server, with the router passing request data to a back-end handler. The struct of the default router: - - type ServeMux struct { - mu sync.RWMutex // because of concurrency, we have to use a mutex here - m map[string]muxEntry // router rules, every string mapping to a handler - } - +```Go +type ServeMux struct { + mu sync.RWMutex // because of concurrency, we have to use a mutex here + m map[string]muxEntry // router rules, every string mapping to a handler +} +``` The struct of muxEntry: - - type muxEntry struct { - explicit bool // exact match or not - h Handler - } - +```Go +type muxEntry struct { + explicit bool // exact match or not + h Handler +} +``` The interface of Handler: - - type Handler interface { - ServeHTTP(ResponseWriter, *Request) // routing implementer - } - +```Go +type Handler interface { + ServeHTTP(ResponseWriter, *Request) // routing implementer +} +``` `Handler` is an interface, but if the function `sayhelloName` didn't implement this interface, then how did we add it as handler? The answer lies in another type called `HandlerFunc` in the `http` package. We called `HandlerFunc` to define our `sayhelloName` method, so `sayhelloName` implemented `Handler` at the same time. It's like we're calling `HandlerFunc(f)`, and the function `f` is force converted to type `HandlerFunc`. +```Go +type HandlerFunc func(ResponseWriter, *Request) - type HandlerFunc func(ResponseWriter, *Request) - - // ServeHTTP calls f(w, r). - func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { - f(w, r) - } - +// ServeHTTP calls f(w, r). +func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) +} +``` How does the router call handlers after we set the router rules? The router calls `mux.handler.ServeHTTP(w, r)` when it receives requests. In other words, it calls the `ServeHTTP` interface of the handlers which have implemented it. Now, let's see how `mux.handler` works. +```Go +func (mux *ServeMux) handler(r *Request) Handler { + mux.mu.RLock() + defer mux.mu.RUnlock() - func (mux *ServeMux) handler(r *Request) Handler { - mux.mu.RLock() - defer mux.mu.RUnlock() - - // Host-specific pattern takes precedence over generic ones - h := mux.match(r.Host + r.URL.Path) - if h == nil { - h = mux.match(r.URL.Path) - } - if h == nil { - h = NotFoundHandler() - } - return h + // Host-specific pattern takes precedence over generic ones + h := mux.match(r.Host + r.URL.Path) + if h == nil { + h = mux.match(r.URL.Path) } - + if h == nil { + h = NotFoundHandler() + } + return h +} +``` The router uses the request's URL as a key to find the corresponding handler saved in the map, then calls handler.ServeHTTP to execute functions to handle the data. You should understand the default router's work flow by now, and Go actually supports customized routers. The second argument of `ListenAndServe` is for configuring customized routers. It's an interface of `Handler`. Therefore, any router that implements the `Handler` interface can be used. The following example shows how to implement a simple router. +```Go +package main - package main +import ( + "fmt" + "net/http" +) - import ( - "fmt" - "net/http" - ) +type MyMux struct { +} - type MyMux struct { +func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/" { + sayhelloName(w, r) + return } + http.NotFound(w, r) + return +} - func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/" { - sayhelloName(w, r) - return - } - http.NotFound(w, r) - return - } +func sayhelloName(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello myroute!") +} - func sayhelloName(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello myroute!") - } - - func main() { - mux := &MyMux{} - http.ListenAndServe(":9090", mux) - } +func main() { + mux := &MyMux{} + http.ListenAndServe(":9090", mux) +} +``` #Routing If you do not want to use a Router, you can still achieve what we wrote in the above section by replacing the second argument to `ListenAndServe` to nil and registering the URLs using a `HandleFunc` function which goes through all the registered URLs to find the best match, so care must be taken about the order of the registering. sample code: - - http.HandleFunc("/", views.ShowAllTasksFunc) - http.HandleFunc("/complete/", views.CompleteTaskFunc) - http.HandleFunc("/delete/", views.DeleteTaskFunc) +```Go +http.HandleFunc("/", views.ShowAllTasksFunc) +http.HandleFunc("/complete/", views.CompleteTaskFunc) +http.HandleFunc("/delete/", views.DeleteTaskFunc) - //ShowAllTasksFunc is used to handle the "/" URL which is the default ons - //TODO add http404 error - func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - context := db.GetTasks("pending") //true when you want non deleted tasks - //db is a package which interacts with the database - if message != "" { - context.Message = message - } - homeTemplate.Execute(w, context) - message = "" - } else { - message = "Method not allowed" - http.Redirect(w, r, "/", http.StatusFound) - } - } - +//ShowAllTasksFunc is used to handle the "/" URL which is the default ons +//TODO add http404 error +func ShowAllTasksFunc(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + context := db.GetTasks("pending") //true when you want non deleted tasks + //db is a package which interacts with the database + if message != "" { + context.Message = message + } + homeTemplate.Execute(w, context) + message = "" + } else { + message = "Method not allowed" + http.Redirect(w, r, "/", http.StatusFound) + } +} +``` This is fine for simple applications which doesn't requires parameterized routing, what when you need that? You can either use the existing toolkits or frameworks, but since this book is about writing webapps in golang, we are going to teach how to handle this scenario as well. When the match is made on the `HandleFunc` function, the URL is matched, so suppose we are writing a todo list manager and we want to delete a task so the URL we decide for that application is `/delete/1`, so we register the delete URL like this `http.HandleFunc("/delete/", views.DeleteTaskFunc)` `/delete/1` this URL matches closest with the "/delete/" URL than any other URL so in the `r.URL.path` we get the entire URL of the request. - - http.HandleFunc("/delete/", views.DeleteTaskFunc) - //DeleteTaskFunc is used to delete a task, trash = move to recycle bin, delete = permanent delete - func DeleteTaskFunc(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - id := r.URL.Path[len("/delete/"):] - if id == "all" { - db.DeleteAll() - http.Redirect(w, r, "/", http.StatusFound) - } else { - id, err := strconv.Atoi(id) - if err != nil { - fmt.Println(err) - } else { - err = db.DeleteTask(id) - if err != nil { - message = "Error deleting task" - } else { - message = "Task deleted" - } - http.Redirect(w, r, "/", http.StatusFound) - } - } - } else { - message = "Method not allowed" - http.Redirect(w, r, "/", http.StatusFound) - } - } - +```Go +http.HandleFunc("/delete/", views.DeleteTaskFunc) +//DeleteTaskFunc is used to delete a task, trash = move to recycle bin, delete = permanent delete +func DeleteTaskFunc(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" { + id := r.URL.Path[len("/delete/"):] + if id == "all" { + db.DeleteAll() + http.Redirect(w, r, "/", http.StatusFound) + } else { + id, err := strconv.Atoi(id) + if err != nil { + fmt.Println(err) + } else { + err = db.DeleteTask(id) + if err != nil { + message = "Error deleting task" + } else { + message = "Task deleted" + } + http.Redirect(w, r, "/", http.StatusFound) + } + } + } else { + message = "Method not allowed" + http.Redirect(w, r, "/", http.StatusFound) + } +} +``` link: https://github.com/thewhitetulip/Tasks/blob/master/views/views.go#L170-#L195 In this above method what we basically do is in the function which handles the `/delete/` URL we take its compelete URL, which is `/delete/1`, then we take a slice of the string and extract everything which starts after the delete word which is the actual parameter, in this case it is `1`. Then we use the `strconv` package to convert it to an integer and delete the task with that taskID. From 4faa8d47288bae433ea6e34ac6ac41ac402e92d5 Mon Sep 17 00:00:00 2001 From: Dmitry Harnitski Date: Wed, 19 Apr 2017 22:56:52 -0400 Subject: [PATCH 2/3] 03.4 - fix Routing title --- en/03.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/03.4.md b/en/03.4.md index 45eb19b0..123db21e 100644 --- a/en/03.4.md +++ b/en/03.4.md @@ -105,7 +105,7 @@ func main() { } ``` -#Routing +# Routing If you do not want to use a Router, you can still achieve what we wrote in the above section by replacing the second argument to `ListenAndServe` to nil and registering the URLs using a `HandleFunc` function which goes through all the registered URLs to find the best match, so care must be taken about the order of the registering. From 4446f727ea022d89395db48d649f2cb0edef31bc Mon Sep 17 00:00:00 2001 From: Dmitry Harnitski Date: Wed, 19 Apr 2017 23:22:28 -0400 Subject: [PATCH 3/3] 03.4.md - use DELETE HTTP method In REST we should use DELETE HTTP method for deletion - http://www.restapitutorial.com/lessons/httpmethods.html --- en/03.4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/en/03.4.md b/en/03.4.md index 123db21e..b5b47bbb 100644 --- a/en/03.4.md +++ b/en/03.4.md @@ -141,7 +141,7 @@ When the match is made on the `HandleFunc` function, the URL is matched, so supp http.HandleFunc("/delete/", views.DeleteTaskFunc) //DeleteTaskFunc is used to delete a task, trash = move to recycle bin, delete = permanent delete func DeleteTaskFunc(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { + if r.Method == "DELETE" { id := r.URL.Path[len("/delete/"):] if id == "all" { db.DeleteAll()