go编写web server的几种方式

先说一下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 main

import (
"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 //TCP address to listen on
Handler Handler //handler to invoke
ReadTimeout time.Duration //maximum duration before timing out read of the request
WriteTimeout time.Duration //maximum duration before timing out write of the response
TLSConfig *tls.Config
...
}
//Handler是一个interface,定义如下
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 {
//handle error
}
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 {
//handle error
}
go handleConn(conn)
}
}

func handleConn(from net.Conn) {
to, err := net.Dial("tcp", ":8001") //建立和目标服务器的连接
if err != nil {
//handle error
}

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稍微强化一点就可以实现#不可描述#的目的了。