Go语言HTTP服务最佳实践译

自从go语言r59版本(一个1.0之前的版本)以来,我一直在写Go程序,并且在过去七年里一直在Go中构建HTTP API和服务. 多年来,我编写服务的方式发生了变化,所以我想分享今天如何编写服务 - 以防模式对您和您的工作有用. 1. Server Struct我的所有组件都有一个server结构,通常看起来像这样: type server struct { db * someDatabase router * someRouter email EmailSender }共享依赖项是结构的字段 2. routes.go我在每个组件中都有一个文件routes.go,其中所有路由都可以存在: package app func(s * server)routes(){ s.router.HandleFunc("/ api/",s.handleAPI()) s.router.HandleFunc("/ about",s.handleAbout()) s.router .HandleFunc("/",s.handleIndex())}这很方便,因为大多数代码维护都是从URL和错误报告开始的,所以只需一眼就routes.go可以指示我们在哪里查看. 3. server 挂载 handler我的HTTPserver 挂载 handler: func(s * server)handleSomething()http.HandlerFunc {...}handler可以通过s服务器变量访问依赖项. 4. return Handler我的处理函数实际上并不处理Request,它们返回一个handler函数. 这给了我们一个闭包环境,我们的处理程序可以在其中运行 func(s * server)handleSomething()http.HandlerFunc { thing:= prepareThing() return func(w http.ResponseWriter,r * http.Request){ // use thing } }该prepareThing只调用一次,所以你可以用它做一次每处理程序初始化,然后用thing在处理程序. 确保只读取共享数据,如果处理程序正在修改任何内容,请记住您需要一个互斥锁或其他东西来保护它. ...

August 28, 2019 · 2 min · jiezi

Django22中间件详解

中间件是 Django 用来处理请求和响应的钩子框架。它是一个轻量级的、底层级的“插件”系统,用于全局性地控制Django 的输入或输出,可以理解为内置的app或者小框架。 在django.core.handlers.base模块中定义了如何接入中间件,这也是学习Django源码的入口之一。 每个中间件组件负责实现一些特定的功能。例如,Django 包含一个中间件组件 AuthenticationMiddleware,它使用会话机制将用户与请求request关联起来。 中间件可以放在你的工程的任何地方,并以Python路径的方式进行访问。 Django 具有一些内置的中间件,并自动开启了其中的一部分,我们可以根据自己的需要进行调整。 一、如何启用中间件若要启用中间件组件,请将其添加到 Django 配置文件settings.py的 MIDDLEWARE 配置项列表中。 在 MIDDLEWARE 中,中间件由字符串表示。这个字符串以圆点分隔,指向中间件工厂的类或函数名的完整 Python 路径。下面是使用 django-admin startproject命令创建工程后,默认的中间件配置: MIDDLEWARE = [  'django.middleware.security.SecurityMiddleware',  'django.contrib.sessions.middleware.SessionMiddleware',  'django.middleware.common.CommonMiddleware',  'django.middleware.csrf.CsrfViewMiddleware',  'django.contrib.auth.middleware.AuthenticationMiddleware',  'django.contrib.messages.middleware.MessageMiddleware',  'django.middleware.clickjacking.XFrameOptionsMiddleware', ]实际上在Django中可以不使用任何中间件,如果你愿意的话,MIDDLEWARE 配置项可以为空。但是强烈建议至少使用 CommonMiddleware。而笔者的建议是保持默认的配置,这有助于你提高网站的安全性。 二、 中间件最关键的顺序问题MIDDLEWARE 的顺序很重要,具有先后关系,因为有些中间件会依赖其他中间件。例如: AuthenticationMiddleware 需要在会话中间件中存储的经过身份验证的用户信息,因此它必须在 SessionMiddleware 后面运行 。 在请求阶段,调用视图之前,Django 按照定义的顺序执行中间件 MIDDLEWARE,自顶向下。 你可以把它想象成一个洋葱:每个中间件类都是一个“皮层”,它包裹起了洋葱的核心--实际业务视图。如果请求通过了洋葱的所有中间件层,一直到内核的视图,那么响应将在返回的过程中以相反的顺序再通过每个中间件层,最终返回给用户。 如果某个层的执行过程认为当前的请求应该被拒绝,或者发生了某些错误,导致短路,直接返回了一个响应,那么剩下的中间件以及核心的视图函数都不会被执行。 三、Django内置的中间件Django内置了下面这些中间件,满足了我们一般的需求: Cache缓存中间件 如果启用了该中间件,Django会以CACHE_MIDDLEWARE_SECONDS 配置的参数进行全站级别的缓存。 Common通用中间件 该中间件为我们提供了一些便利的功能: 禁止DISALLOWED_USER_AGENTS中的用户代理访问服务器自动为URL添加斜杠后缀和www前缀功能。如果配置项 APPEND_SLASH 为True ,并且访问的URL 没有斜杠后缀,在URLconf中没有匹配成功,将自动添加斜杠,然后再次匹配,如果匹配成功,就跳转到对应的url。 PREPEND_WWW 的功能类似。为非流式响应设置Content-Length头部信息。作为展示的例子,这里额外贴出它的源代码,位于django.middleware.common模块中,比较简单,很容易读懂和理解: class CommonMiddleware(MiddlewareMixin): """ 去掉了doc """ response_redirect_class = HttpResponsePermanentRedirect def process_request(self, request): # Check for denied User-Agents if 'HTTP_USER_AGENT' in request.META: for user_agent_regex in settings.DISALLOWED_USER_AGENTS: if user_agent_regex.search(request.META['HTTP_USER_AGENT']): raise PermissionDenied('Forbidden user agent') # Check for a redirect based on settings.PREPEND_WWW host = request.get_host() must_prepend = settings.PREPEND_WWW and host and not host.startswith('www.') redirect_url = ('%s://www.%s' % (request.scheme, host)) if must_prepend else '' # Check if a slash should be appended if self.should_redirect_with_slash(request): path = self.get_full_path_with_slash(request) else: path = request.get_full_path() # Return a redirect if necessary if redirect_url or path != request.get_full_path(): redirect_url += path return self.response_redirect_class(redirect_url) def should_redirect_with_slash(self, request): if settings.APPEND_SLASH and not request.path_info.endswith('/'): urlconf = getattr(request, 'urlconf', None) return ( not is_valid_path(request.path_info, urlconf) and is_valid_path('%s/' % request.path_info, urlconf) ) return False def get_full_path_with_slash(self, request): new_path = request.get_full_path(force_append_slash=True) if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'): raise RuntimeError( "You called this URL via %(method)s, but the URL doesn't end " "in a slash and you have APPEND_SLASH set. Django can't " "redirect to the slash URL while maintaining %(method)s data. " "Change your form to point to %(url)s (note the trailing " "slash), or set APPEND_SLASH=False in your Django settings." % { 'method': request.method, 'url': request.get_host() + new_path, } ) return new_path def process_response(self, request, response): # If the given URL is "Not Found", then check if we should redirect to # a path with a slash appended. if response.status_code == 404: if self.should_redirect_with_slash(request): return self.response_redirect_class(self.get_full_path_with_slash(request)) if settings.USE_ETAGS and self.needs_etag(response): warnings.warn( "The USE_ETAGS setting is deprecated in favor of " "ConditionalGetMiddleware which sets the ETag regardless of " "the setting. CommonMiddleware won't do ETag processing in " "Django 2.1.", RemovedInDjango21Warning ) if not response.has_header('ETag'): set_response_etag(response) if response.has_header('ETag'): return get_conditional_response( request, etag=response['ETag'], response=response, ) # Add the Content-Length header to non-streaming responses if not # already set. if not response.streaming and not response.has_header('Content-Length'): response['Content-Length'] = str(len(response.content)) return response def needs_etag(self, response): """Return True if an ETag header should be added to response.""" cache_control_headers = cc_delim_re.split(response.get('Cache-Control', '')) return all(header.lower() != 'no-store' for header in cache_control_headers)GZip内容压缩中间件 ...

August 7, 2019 · 4 min · jiezi

TKoa - 使用 TypeScript 重构的 Node.js Koa开发框架

????Tkoa是使用 typescript 编写的 koa 框架!尽管它是基于 typescript 编写,但是你依然还是可以使用一些 node.js 框架和基于 koa 的中间件。不仅如此,你还可以享受 typescript 的类型检查系统和方便地使用 typescript 进行测试!安装TKoa 需要 >= typescript v3.1.0 和 node v7.6.0 版本。$ npm install tkoaHello T-koaimport tKoa = require(’tkoa’);interface ctx { res: { end: Function }}const app = new tKoa();// responseapp.use((ctx: ctx) => { ctx.res.end(‘Hello T-koa!’);});app.listen(3000);MiddlewareTkoa 是一个中间件框架,拥有两种中间件:异步中间件普通中间件下面是一个日志记录中间件示例,其中使用了不同的中间件类型:async functions (node v7.6+):interface ctx { method: string, url: string}app.use(async (ctx: ctx, next: Function) => { const start = Date.now(); await next(); const ms = Date.now() - start; console.log(${ctx.method} ${ctx.url} - ${ms}ms);});Common function// Middleware normally takes two parameters (ctx, next), ctx is the context for one request,// next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.interface ctx { method: string, url: string}app.use((ctx: ctx, next: Function) => { const start = Date.now(); return next().then(() => { const ms = Date.now() - start; console.log(${ctx.method} ${ctx.url} - ${ms}ms); });});Getting startedTkoa - 教程en - english readmeGitter - 聊天室SupportTypeScript大于等于 v3.1 版本Node.js大于等于 v7.6.0 版本LicenseMIT ...

April 17, 2019 · 1 min · jiezi

GO中间件(Middleware )

中间件是一种计算机软件,可为操作系统提供的软件应用程序提供服务,以便于各个软件之间的沟通,特别是系统软件和应用软件。广泛用于web应用和面向服务的体系结构等。纵观GO语言,中间件应用比较普遍,主要应用:记录对服务器发送的请求(request)处理服务器响应(response )请求和处理之间做一个权限认证工作远程调用安全等等中间件处理程序是简单的http.Handler,它包装另一个http.Handler做请求的一些预处理和/或后处理。它被称为“中间件”,因为它位于Go Web服务器和实际处理程序之间的中间位置。下面是一些中间件例子记录日志中间件package mainimport ( “fmt” “log” “net/http”)func logging(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { log.Println(r.URL.Path) f(w, r) }}func foo(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, “foo”)}func bar(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, “bar”)}func main() { http.HandleFunc("/foo", logging(foo)) http.HandleFunc("/bar", logging(bar)) http.ListenAndServe(":8080", nil)}访问 http://localhost:8080/foo返回结果foo将上面示例修改下,也可以实现相同的功能。package mainimport ( “fmt” “log” “net/http”)func foo(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, “foo”)}func bar(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, “bar”)}func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.URL.Path) next.ServeHTTP(w, r) })}func main() { http.Handle("/foo", loggingMiddleware(http.HandlerFunc(foo))) http.Handle("/bar", loggingMiddleware(http.HandlerFunc(bar))) http.ListenAndServe(":8080", nil)}访问 http://localhost:8080/foo返回结果foo多中间件例子package mainimport ( “fmt” “log” “net/http” “time”)type Middleware func(http.HandlerFunc) http.HandlerFunc// Logging logs all requests with its path and the time it took to processfunc Logging() Middleware { // Create a new Middleware return func(f http.HandlerFunc) http.HandlerFunc { // Define the http.HandlerFunc return func(w http.ResponseWriter, r *http.Request) { // Do middleware things start := time.Now() defer func() { log.Println(r.URL.Path, time.Since(start)) }() // Call the next middleware/handler in chain f(w, r) } }}// Method ensures that url can only be requested with a specific method, else returns a 400 Bad Requestfunc Method(m string) Middleware { // Create a new Middleware return func(f http.HandlerFunc) http.HandlerFunc { // Define the http.HandlerFunc return func(w http.ResponseWriter, r *http.Request) { // Do middleware things if r.Method != m { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // Call the next middleware/handler in chain f(w, r) } }}// Chain applies middlewares to a http.HandlerFuncfunc Chain(f http.HandlerFunc, middlewares …Middleware) http.HandlerFunc { for _, m := range middlewares { f = m(f) } return f}func Hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, “hello world”)}func main() { http.HandleFunc("/", Chain(Hello, Method(“GET”), Logging())) http.ListenAndServe(":8080", nil)}中间件本身只是将其http.HandlerFunc作为其参数之一,包装它并返回一个新http.HandlerFunc的服务器来调用。在这里,我们定义了一种新类型Middleware,最终可以更容易地将多个中间件链接在一起。当然我们也可以改成如下形式package mainimport ( “fmt” “log” “net/http” “time”)type Middleware func(http.Handler) http.Handlerfunc Hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, “hello world”)}func Chain(f http.Handler, mmap …Middleware) http.Handler { for _, m := range mmap { f = m(f) } return f}func Method(m string) Middleware { return func(f http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.URL.Path) if r.Method != m { http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } f.ServeHTTP(w, r) }) }}func Logging() Middleware { return func(f http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //log.Println(r.URL.Path) // Do middleware things start := time.Now() defer func() { log.Println(r.URL.Path, time.Since(start)) }() f.ServeHTTP(w, r) }) }}func main() { http.Handle("/", Chain(http.HandlerFunc(Hello), Method(“GET”), Logging())) http.ListenAndServe(":8080", nil)}在gin框架下实现中间件r := gin.Default() 创建带有默认中间件的路由,默认是包含logger和recovery中间件的r :=gin.new() 创建带有没有中间件的路由示例package mainimport ( “github.com/gin-gonic/gin” “log” “time”)func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set(“example”, “12345”) // before request c.Next() // after request latency := time.Since(t) log.Print(latency) //时间 0s // access the status we are sending status := c.Writer.Status() log.Println(status) //状态 200 }}func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet(“example”).(string) // it would print: “12345” log.Println(example) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080")}以上示例也可改为package mainimport ( “github.com/gin-gonic/gin” “log” “time”)func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set(“example”, “12345”) // before request c.Next() // after request latency := time.Since(t) log.Print(latency) //时间 0s // access the status we are sending status := c.Writer.Status() log.Println(status) //状态 200 }}func main() { r := gin.New() r.GET("/test", Logger(), func(c *gin.Context) { example := c.MustGet(“example”).(string) // it would print: “12345” log.Println(example) }) // Listen and serve on 0.0.0.0:8080 r.Run(":8080")}即不用r.use添加中间件,直接将Logger() 写到r.GET 方法的参数里("/test"之后)。更多gin中间件示例可参考 https://github.com/gin-gonic/gin参考资料https://drstearns.github.io/t…https://gowebexamples.com/adv…更多GO学习资料 ...

April 10, 2019 · 3 min · jiezi

express.js中间件说明

express的新开发人员往往对路由处理程序和中间件之间的区别感到困惑。因此他们也对app.use(),app.all(),app.get(),app.post(),app.delete()和app.put()方法的区别感到困惑。在本文中,我将解释中间件和路由处理程序之间的区别。以及如何正确使用app.use(),app.all(),app.get(),app.post(),app.delete()和app.put()方法。路由处理app.use(),app.all(),app.get(),app.post(),app.delete()和app.put()全部是用来定义路由的。这些方法都用于定义路由。路由用于处理HTTP请求。路由是路径和回调的组合,在请求的路径匹配时执行。回调被称为路由处理程序。它们之间的区别是处理不同类型的HTTP请求。例如: app.get()方法仅仅处理get请求,而app.all()处理GET、POST等请求。下面是一个例子,如何定义一个路由:var app = require(“express”)();app.get("/", function(req, res, next){ res.send(“Hello World!!!!”);});app.listen(8080);每个路由处理程序都获得对当前正在提供的HTTP请求的请求和响应对象的引用。可以为单个HTTP请求执行多个路由处理程序。这是一个例子:var app = require(“express”)();app.get("/", function(req, res, next){ res.write(“Hello”); next();});app.get("/", function(req, res, next){ res.write(" World !!!"); res.end();});app.listen(8080);这里第一个句柄写入一些响应,然后调用next()。 next()方法用于调用与路径路径匹配的下一个路由处理程序。路由处理程序必须结束请求或调用下一个路由处理程序。我们还可以将多个路由处理程序传递给app.all(),app.get(),app.post(),app.delete()和app.put()方法。这是一个证明这一点的例子:var app = require(“express”)();app.get("/", function(req, res, next){ res.write(“Hello”); next();}, function(req, res, next){ res.write(" World !!!"); res.end();});app.listen(8080);中间件中间件是一个位于实际请求处理程序之上的回调。它采用与路由处理程序相同的参数。要了解中间件,我们来看一个带有dashboard和profile页面的示例站点。要访问这些页面,用户必须登录。还会记录对这些页面的请求。以下是这些页面的路由处理程序的代码:var app = require(“express”)();function checkLogin(){ return false;}function logRequest(){ console.log(“New request”);}app.get("/dashboard", function(req, res, next){ logRequest(); if(checkLogin()){ res.send(“This is the dashboard page”); } else{ res.send(“You are not logged in!!!”); }});app.get("/profile", function(req, res, next){ logRequest(); if(checkLogin()){ res.send(“This is the dashboard page”); } else{ res.send(“You are not logged in!!!”); }});app.listen(8080);这里的问题是有很多重复的代码,即我们不得不多次使用logRequest()和checkLogin()函数。这也使得更新代码变得困难。因此,为了解决这个问题,我们可以为这两条路径编写一条通用路径。这是重写的代码:var app = require(“express”)();function checkLogin(){ return false;}function logRequest(){ console.log(“New request”);}app.get("/", function(req, res, next){ logRequest(); next();})app.get("/", function(req, res, next){ if(checkLogin()){ next(); } else{ res(“You are not logged in!!!”); }})app.get("/dashboard", function(req, res, next){ res.send(“This is the dashboard page”);});app.get("/profile", function(req, res, next){ res.send(“This is the dashboard page”);});app.listen(8080);这里的代码看起来更清晰,更易于维护和更新。这里将前两个定义的路由处理程序称为中间件,因为它们不处理请求,而是负责预处理请求。Express为我们提供了app.use()方法,该方法专门用于定义中间件。 app.use()方法可能看起来与app.all()类似,但它们之间存在很多差异,这使得app.use()非常适合于声明中间件。让我们看看app.use()方法是如何工作的:app.use() 和 app.all() 的不同:CALLBACKapp.use()只需要一个回调,而app.all()可以进行多次回调。PATHapp.use()只查看url是否以指定路径开头,app.all()匹配完整路径。这里有一个例子来说明:app.use( “/product” , mymiddleware);// will match /product// will match /product/cool// will match /product/fooapp.all( “/product” , handler);// will match /product// won’t match /product/cool <– important// won’t match /product/foo <– importantapp.all( “/product/” , handler);// won’t match /product <– Important// will match /product/cool// will match /product/fooNEXT()中间件内的next()调用下一个中间件或路由处理程序,具体取决于接下来声明的那个。但是路由处理程序中的next()仅调用下一个路由处理程序。如果接下来有中间件,则跳过它。因此,必须在所有路由处理程序之前声明中间件。这里有一个例子来说明:var express = require(’express’);var app = express();app.use(function frontControllerMiddlewareExecuted(req, res, next){ console.log(’(1) this frontControllerMiddlewareExecuted is executed’); next();});app.all(’’, function(req, res, next){ console.log(’(2) route middleware for all method and path pattern “*”, executed first and can do stuff before going next’); next();});app.all(’/hello’, function(req, res, next){ console.log(’(3) route middleware for all method and path pattern “/hello”, executed second and can do stuff before going next’); next();});app.use(function frontControllerMiddlewareNotExecuted(req, res, next){ console.log(’(4) this frontControllerMiddlewareNotExecuted is not executed’); next();});app.get(’/hello’, function(req, res){ console.log(’(5) route middleware for method GET and path patter “/hello”, executed last and I do my stuff sending response’); res.send(‘Hello World’);});app.listen(80);现在我们看到了app.use()方法的唯一性以及它用于声明中间件的原因。让我们重写我们的示例站点代码:var app = require(“express”)();function checkLogin(){ return false;}function logRequest(){ console.log(“New request”);}app.use(function(req, res, next){ logRequest(); next();})app.use(function(req, res, next){ if(checkLogin()){ next(); } else{ res.send(“You are not logged in!!!”); }})app.get("/dashboard", function(req, res, next){ res.send(“This is the dashboard page”);});app.get("/profile", function(req, res, next){ res.send(“This is the dashboard page”);});app.listen(8080); ...

March 18, 2019 · 2 min · jiezi