This is a creation in Article, where the information may have evolved or changed.
Overview
Recently engaged in their own go web development framework, anyway, there is no intention to stash, so now take out the URL routing design this piece to write a blog. have done web development know, a good URL routing can make the user's browser address bar always have rules to follow, can let us develop the site easier to let search engine included, can let our developers more convenient MVC. When we use other web development frameworks, URL routing will certainly be a key feature of the framework or a promotional "selling point." So, the status of URL routing in a Web framework is still very important.
Back to go web development, how to use go to implement a URL routing function? How does the code write after implementation? Let's move on to a simple URL routing feature.
How to use
Before we learn how to achieve this, we must first look at how to use it. It's really easy to use, because I've written a PHP Web development framework before, so our routing section is used like PHP (thinkphp). Let's take a look at the code.
package mainimport ( "./app" "./controller")func main() { app.Static["/static""./js" app.AutoRouter(&controller.IndexController{}) app.RunOn(":8080")}
Three lines of code, the role of the first line everyone should be clear, is to serve some static files (such as JS, CSS and other files), the second line of code is to register a controller, this line of code in PHP is not, after all, PHP is a dynamic language, one __autoload
can complete the loading of classes, And go as a static language does not have this feature, so we still need to manually register (think about it, this can be like Java in the configuration file?) This function is left to be added later when optimization. and the last line of code doesn't say, it's actually starting the server. Here we are listening on port 8080.
The code above is simple, let's take a look at how that is IndexController
written.
PackageControllerImport(".. /app " ".. /funcs " "Html/template")typeIndexcontrollerstruct{app. APP}func(I *indexcontroller) Index () {i.data["Name"] ="Qibin"i.data["Email"] ="Qibin0506@gmail.com" //i.display ("./view/info.tpl", "./view/header.tpl", "./view/footer.tpl")I.displaywithfuncs (template. funcmap{"Look": Funcs. Lookup},"./view/info.tpl","./view/header.tpl","./view/footer.tpl")}
First we define a struct, which is an anonymous combination of the struct App
(in object-oriented terms, which is inherited), but we define a method for him, and Index
we don't have to care about what we do. How do we get to the access? Now run the code, in the browser input http:/ /localhost:8080 or input http://localhost:8080/index/index can see the content we Index
output in the method, specifically how to do, in fact, this is completely the credit of the URL route, Let's start by preparing to design such a URL routing feature.
Design of URL Routing
What AutoRouter
does it look like? Let's take a look at how this is done with the ability to register a route.
PackageAppImport("Reflect" "Strings")varMappingMap[string]reflect. Type = Make(Map[string]reflect. Type)funcRouter (patternstring, t reflect. Type) {mapping[strings. ToLower (pattern)] = t}funcRouter (Patternstring, App IApp) {REFV: = reflect. ValueOf (APP) Reft: = reflect. Indirect (REFV). Type () router (pattern, reft)}funcAutorouter (App IApp) {REFV: = reflect. ValueOf (APP) Reft: = reflect. Indirect (REFV). Type () RefName: = Strings. Trimsuffix (Strings. ToLower (Reft.name ()),"Controller") Router (refName, Reft)}
First we define a map
variable, and his key is a string type, and we suspect that it is certain part of the URL that we entered in the browser, and then we pass it to get to the concrete to execute a struct. What about his value? reflect.Type
, we see AutoRouter
the implementation of the code is clear. In the AutoRouter
first we used reflect.ValueOf
to get to the structure we registered, and Value
then we acquired it Type
, and finally we put this pair of string,type to map. But the code here is just to explain how to register in, and do not explain why to save Type
Ah, here secretly tell you, in fact, for each visit, we find that the corresponding is Controller
not also must not be directly called the structure of the method, but by reflection to create a new instance to invoke. The specific code we'll talk about later.
So far, our route has been successfully registered, although we have some doubts about the preservation. Let's start with the Type
RunOn
function to see how it is based on the routing registry to find the corresponding Controller
and its methods.
First look at RunOn
the code.
funcstring) { server := &http.Server{ Handler: newHandler(), Addr: port, } log.Fatal(server.ListenAndServe())}
This code is also very simple, for the familiar with the go web development students should be very familiar with Server
, Handler
we are through a newHandler
function to return, this newHandler
did what?
func newHandler() *handler { h := &handler{} funcinterface{} { return &Context{} } return h}
First constructs a handler
, then gives the handler in the sync.Pool
assignment, this thing is why, we later will say in detail, below we come to feel relieved to see handler
how this structure design.
typestruct { p sync.Pool}
Very simple, for the p
above said, in the following we will say in detail, for handler
we believe it must have a method called ServeHTTP
, to see it.
func (H *handler) Servehttp (w http. Responsewriter, R *http. Request) {if servestatic (W, R) {return } CTX: = H.p.get (). (*context) defer h.p.put (CTX) ctx. Config (W, R) controllername, MethodName: = H.findcontrollerinfo (R) Controllert, OK: = Mapping[controllername]
if !ok {http. NotFound (W, R)
return } REFV: = Reflect. New (Controllert) Method: = Refv.methodbyname (methodName)
if !method. IsValid () {http. NotFound (W, R)
return } Controller: = Refv.interface (). (IAPP) controller. Init (CTX) method. Call (
nil )}
The code in this is actually the core code of our routing design, let's take a look at how this code is implemented in detail here. The first three lines of code are our support for static files.
Then we use it sync.Pool
, first we take one from the inside, Context
and after this method is executed Context
, put this in, what is the purpose of this? In fact, our site is not a single-line, so here is ServeHTTP
not only for a user, and in our Controller
also must be saved ResponseWriter
and Request
other information, so in order to prevent the request of the information will be rewritten by other requests, we choose to use the object pool here, when used to take out, after the use of the time to go in, before each use of the information to refresh, This avoids the error of not having to ask for the information to be rewritten. For sync.Pool
A brief explanation here, is there a field assignment that we have given him? The New
logic in this is that when we take it from this, pool
if it doesn't, it will be used to New
create a new one, So here we can guarantee the Context
only premise, but also to ensure that every time we pool
get from the always get.
Continue to look at the code, and then we are going through the findControllerInfo
URL to parse out the name we want to execute, controller
method
go down, we create a new object by reflection controller
, and through to MethodByName
get to execute the method. Specific code:
refV := reflect.New(controllerT)method := refV.MethodByName(methodName)
This explains why the above is saved reflect.Type
. Finally, we'll Context
set this up and Controller
call the method we found. The General URL route is this, mainly through the go reflection mechanism to find the structure to be executed and the specific method to execute, Then the call is ready. However, one of the other things that we haven't said is that findControllerInfo
the implementation is relatively simple, that is, the URL to find controller
and the name of the method we want to execute. Take a look at the code:
func(H *handler) Findcontrollerinfo (R *http. Request) (string,string{path: = R.url. PathifStrings. Hassuffix (Path,"/") {Path = strings. Trimsuffix (Path,"/")} PathInfo: = Strings. Split (Path,"/") Controllername: = Defcontrollerif Len(PathInfo) >1{controllername = PathInfo[1]} methodName: = Defmethodif Len(PathInfo) >2{MethodName = strings. Title (Strings. ToLower (PathInfo[2])) }returnControllername, MethodName}
Here first we get the URL pathInfo
, for example, for request Http://localhost:8080/user/info, here we are going to get this user
and info
, but for http://localhost : 8080 or http://localhost:8080/user? We'll have the default, too.
const ( "index" defMethod "Index")
To the current location, our URL route has basically been formed, but there are a few points we have not shot, such as the above often seen App
and Context
. First, let's take a look at this Context
, what's this? In fact, Context
we have a simple encapsulation of the request information.
package appimport ( "net/http")typeinterface { Config(w http.ResponseWriter, r *http.Request)}typestruct { w http.ResponseWriter r *http.Request}func (c *Context) Config(w http.ResponseWriter, r *http.Request) { c.w = w c.r = r}
Here we simply encapsulate, just save ResponseWriter
and Request
, each time we request the Config
method will be called to the new ResponseWriter
and Request
saved in.
And what about apps? Design is more flexible, in addition to several in handler
-use methods, basically are "on-the-spot play."
typeinterface { Init(ctx *Context) W() http.ResponseWriter R() *http.Request Display(tpls ...string) DisplayWithFuncs(funcs template.FuncMap, tpls ...string)}
The method in this interface should be guessed, the Init
method we have used in the above ServeHTTP
, and W
R
the method is purely for ResponseWriter
the convenience of access and, the Request
following two Display
methods here is not much to say, is to encapsulate the template loading mechanism of go native. Let's see App
how this interface is implemented.
typeAppstruct{CTX *context DataMap[string]Interface{}}func(A *app) Init (ctx *context) {a.ctx = CTX A.data = Make(Map[string]Interface{})}func(A *app) W () http. Responsewriter {returnA.CTX.W}func(A *app) R () *http. Request {returnA.CTX.R}func(A *app) Display (Tpls ...string) {if Len(TPLS) = =0{return} Name: = FilePath. Base (Tpls[0]) T: = template. Must (template. Parsefiles (Tpls ...)) T.executetemplate (A.W (), Name, A.data)}func(A *app) Displaywithfuncs (funcs template. Funcmap, Tpls ...string) {if Len(TPLS) = =0{return} Name: = FilePath. Base (Tpls[0]) T: = template. Must (template. New (name). Funcs (Funcs). Parsefiles (Tpls ...)) T.executetemplate (A.W (), Name, A.data)}
OK, the said above all said, and finally we have not seen is the static file support, here is also very simple.
varStaticMap[string]string= Make(Map[string]string)funcServestatic (w http. Responsewriter, R *http. Request)BOOL{ forprefix, Static: =RangeStatic {ifStrings. Hasprefix (R.url. Path, prefix) {file: = static + R.url. path[Len(prefix):] http. Servefile (W, R, file)return true} }return false}
So far, one of our simple URL routes has been implemented, but our implementation is not perfect, such as custom routing rules are not supported, for PathInfo parameters we have not obtained, which can be completed in the completion stage. In the process of designing this route, we fully refer to some implementation methods of Beego. Reading and understanding someone's code is the right way to read the source when you encounter a problem.
Finally, let's end this article with a single run.