先说一下web server和http server的区别。http server,顾名思义,支持http协议的服务器;web server除了支持http协议可能还支持其他网络协议。本文只讨论使用golang的官方package编写web server的几种常用方式。
最简单的http server 这也是最简单的一种方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "net/http" "log" ) func myHandler (w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello there!\n" ) } func main () { http.HandleFunc("/" , myHandler) log.Fatal(http.ListenAndServe(":8080" , nil )) }
启动程序,另开一个终端输入命令curl localhost:8080
,或者直接浏览器打开localhost:8080
,就可以看到Hello there!
ListenAndServe函数负责监听并处理连接。内部处理方式是对于每个connection起一个goroutine来处理。其实这并不是一种好的处理方式。学习过操作系统的同学都知道,进程或者线程切换的代价是巨大的。虽然goroutine是用户级的轻量级线程,切换并不会导致用户态和内核态的切换,但是当goroutine数量巨大的时候切换的代价还是不容小觑的。更好的一种方式是使用goroutine pool,这里暂且按住不表。
使用Handler接口 上面这种方式感觉可发挥余地太小了,比如我想设置server的Timeout时间都不能设置了。这时候我们就可以使用自定义server了。
1 2 3 4 5 6 7 8 9 10 11 12 type Server struct { Addr string Handler Handler ReadTimeout time.Duration WriteTimeout time.Duration TLSConfig *tls.Config ... } type Handler interface { ServeHTTP(ResponseWrite, *Request) }
所以只要我们实现了Handler接口的方法ServeHTTP
就可以自定义我们的server了。示例代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type myHandler struct { } func (this myHandler) ServeHTTP (w ResponseWrite, r *Request) { ... } func main () { server := http.Server{ Addr: ":8080" , Handler: &myHandler{}, ReadTimeout: 3 *time.Second, ... } log.Fatal(server.ListenAndServe) }
直接处理conn 有时候我们需要更底层一点直接处理connection,这时候可以使用net
包。server端代码简单实现如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func main () { listener, err := net.Listen("tcp" , ":8080" ) if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { } go handleConn(conn) } }
为了示例方便,我这里对于每一个connection都起了一个goroutine去处理。实际使用中,goroutine pool往往是一个更好的选择。对于client的返回信息我们再歇回到conn中就可以。
proxy 上面说了几种web server的实现方式,下面简单实现一个http proxy,用golang写proxy很简单只需要把conn转发就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func main () { listener, err := net.Listen("tcp" , ":8080" ) if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { } go handleConn(conn) } } func handleConn (from net.Conn) { to, err := net.Dial("tcp" , ":8001" ) if err != nil { } done := make (chan struct {}) go func () { defer from.Close() defer to.Close() io.Copy(from, to) done<-strcut{}{} }() go func () { defer from.Close() defer to.Close() io.Copy(to, from) done<-struct {}{} } <-done <-done }
proxy稍微强化一点就可以实现#不可描述#的目的了。