(Go Web) 接受请求

Go Web 接收请求

因为HTTP是一种无连接协议,通过这种协议发送给服务器的请求对服务器之前处理的请求一无所知,所以应用程序才会采取cookie的方式在客户端实现数据持久化,并以会话的方式在服务器上实现数据持久化。为了降低使用cookiesession的复杂性,Web应用框架通常会提供一个统一的接口,用于在连接直接提供持久化。

Go来说,隐藏在框架下的通常是net/httphtml/template

启动Web服务器

最简单的 Web 服务器

package main

import (
	"net/http"
)

func main()  {
	// 用户的网络地址
	http.ListenAndServe("", nil)
}

我们启动了一个一个最简单的服务器,只要调用ListenAndServe并传入网络地址以及负责处理请求的处理器作为参数即可。

  • 网络地址为空,默认采用80端口。
  • 处理器参数为空,服务器将使用默认的DefaultServeMux

带有附加配置的 Web 服务器

package main

import "net/http"

func main() {
	server := http.Server{
		Addr:    "127.0.0.1:8080", // 指定网络地址和端口
		Handler: nil,			  // 采用 DefaultServeMux
	}
	server.ListenAndServe()
	
	// 404 page not found
}

我们也可以直接在http.Server结构中对服务器进行更详细的配置。

我们启动的这个服务器并未实现任何功能,所以访问这个服务器只会获得一个404 page not found。出现这一问题的原因是我们尚未为服务器编写任何处理器,所以服务器的多路复用器在接收到请求后找不到任何处理器来处理请求。

使用自己的处理器

Go中,一个处理器就是一个拥有ServerHTTP方法的接口

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

那么,如果我们的自定义类型实现了ServeHTTP(ResponseWriter, *Request)方法,该类型也可以作为处理器被传入Handler接口

为什么 ListenAndServe的第二个参数是一个处理器,它的默认值却是多路复用器DefaultServeMux呢?

  • 因为DefaultServeMux多路复用器是ServeMux结构的一个实例,而ServeMuxServeHTTP方法,所以DefaultServeMux也可以作为处理器传入。
  • DefaultServeMux既是多路复用器,也是处理器,既是ServeMux的实例,也是Handler的实例
  • DefaultServeMux是一个特殊的处理器,它唯一要做的就是根据请求和URL将请求重定向到不同的处理器
package main

import (
	"fmt"
	"net/http"
)

// 自定义处理器
type MyHandler struct{}

// 自定义 MyHandler 结构实现了 ServeHTTP 方法,我们可以向 handler 接口传递我们自己的处理器了
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World!")
}

func main() {
	handler := MyHandler{}
	server := http.Server{
		Addr:    "127.0.0.1:8080",
		Handler: &handler,
	}
	server.ListenAndServe()
}

向浏览器地址栏输入[127.0.0.1:8080](http://127.0.0.1:8080/),我们可以看到Hello World!

因为我们没有采用默认的多路复用器,而是让服务器与我们自己的处理器绑定。所以无法用到多路复用的将请求重定向到不同页面的特性,而是直接使用同一个处理器来处理不同请求,所以浏览器任何地址访问都是Hello World!

因此大部分情况下,我们还是需要这个多路复用器的存在来支持我们不同的访问请求

使用多个处理器

type Server struct {
    Addr           string        // 监听的TCP地址,如果为空字符串会使用":http"
    Handler        Handler       // 调用的处理器,如为nil会调用http.DefaultServeMux
    ReadTimeout    time.Duration // 请求的读取操作在超时前的最大持续时间
    WriteTimeout   time.Duration // 回复的写入操作在超时前的最大持续时间
    MaxHeaderBytes int           // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
    TLSConfig      *tls.Config   // 可选的TLS配置,用于ListenAndServeTLS方法
    // TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
    // 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
    // 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
    // 连接在函数返回时会自动关闭。
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    // ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
    // 参见ConnState类型和相关常数获取细节。
    ConnState func(net.Conn, ConnState)
    // ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
    // 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
    ErrorLog *log.Logger
    // 内含隐藏或非导出字段
}
package main

import (
	"fmt"
	"net/http"
)

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

type WorldHandler struct{}

func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "World!")
}

// 使用多个处理器对请求进行处理
func main() {
	hello := HelloHandler{}
	world := WorldHandler{}
	server := http.Server{
		Addr: "127.0.0.1:8080",
		// 我们不再指定特定的处理器
	}

	// 让服务器使用默认的 DefaultServeMux 作为处理器
	// 然后通过 http.Handle 函数将处理器绑定至 DefaultServeMux

	// 调用 http.Handle 实际上就是在调用 DefaultServeMux Handle 方法
	http.Handle("/hello", &hello)
	http.Handle("/world", &world)

	server.ListenAndServe()
}
  • 在地址栏输入http://127.0.0.1:8080/hello,可以看到Hello!
  • 在地址栏输入http://127.0.0.1:8080/world,可以看到World!

处理器函数 HandleFunc

package main

import (
	"fmt"
	"net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

func world(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "World!")
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	// 使用 HandleFunc 更加快速得到带方法的 Handler
	// HandleFunc 可以把函数转换成带有方法的 Handler
	http.HandleFunc("/hello", hello)
	http.HandleFunc("/world", world)

	server.ListenAndServe()
}

Go语言拥有一种HandlerFunc函数类型,HandlerFunc type是一个适配器,通过类型转换让我们可以将普通的函数作为HTTP处理器使用。如果f是一个具有适当签名的函数,HandlerFunc(f)通过调用f实现了Handler接口。

type HandlerFunc func(ResponseWriter, *Request)

可以看到HandlerFunc也实现了Handler接口

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
// hello 是一个拥有是适当签名的函数(符合 ServeHTTP)
func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

// 通过调用 hello 实现了 Handler 接口
helloHandler := HandlerFunc(hello) 

HandleFunc函数会将hello函数转换成一个Handler,并将它与DefaultServeMux进行绑定,以此简化创建并绑定Handler的工作

串联多个处理器和处理器函数

倘若我们想要在调用处理器函数时引入日志记录,安全检查等功能,直接写新函数比较麻烦,且会和处理器代码混合在一起,我们可以使用 串联 计数分割代码中的这些操作。

主要使用了函数式编程的特性,函数类型、匿名函数和闭包。

package main

import (
	"fmt"
	"net/http"
	"reflect"
	"runtime"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello!")
}

// 接收一个 http.HandlerFunc 类型的函数作为参数,而 hello 函数就是这个类型
func log(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// 使用反射获取函数名
		name := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
		fmt.Println("Handler function called -" + name)
		// 调用和 hello
		h(w, r)
	}
}

// 相当于在hello处理函数的基础上又封装了一层
func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/hello", log(hello))
	server.ListenAndServe()
}

log函数会打印调用了什么函数,其接收一个HandlerFunc类型的函数作为参数,然后返回另一个HandlerFunc类型的函数。log函数返回一个匿名函数,匿名函数内部程序会首先获取传入的HandlerFunc类型函数名字,然后调用这个函数。

使用其他的多路复用器

市面上已经有许多的第三方多路复用器,这里使用的是HttpRouterServeMux的缺陷是无法使用变量实现URL模式匹配

package main

import (
   "fmt"
   "github.com/julienschmidt/httprouter"
   "net/http"
)

func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
   fmt.Fprintf(w, "hello, %s!\n", p.ByName("name"))
}

func main() {
   mux := httprouter.New()
   // 把处理器函数与给定的 HTTP 方法进行绑定
   // 浏览器向这个URL发送GET请求时,hello函数就会被调用
   mux.GET("/hello/:name", hello)

   server := http.Server{
      Addr:    "127.0.0.1.:8080",
      Handler: mux,
   }
   server.ListenAndServe()
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值