Go利用net/http包搭建Web服务器

原创 2017年05月26日 12:39:46

说明

本文介绍了利用net/http包搭建Web服务器的一般流程。并不涉及Web开发的细节问题。

简介

利用Go的标准包net/http可以很方便的搭建服务器。实际上只需要一个函数和一个接口:

net/http

package http

// 建立服务器,address为服务器地址,比如:"localhost:8000"
// h 是处理请求的接口,类型为 Handler
// 该函数将一直运行,除非有错误发生,则返回error,返回的error永远不为nil
func ListenAndServe(address string, h Handler) error

// Handler 为处理请求的接口
type Handler interface {
    // 该函数处理所有请求
    // r为*Request类型,表示请求对象
    // w为ResponseWriter类型,表示响应对象,我们将应内容写入到w
    ServeHTTP(w ResponseWriter, r *Request)
}

有了以上知识,我们就可以实现一个简单的服务器了:

http1.go

package main

import (
    "log"
    "net/http"
)

// 步骤1:声明自定义类型
type MyHttpHandler struct{}

// 步骤2:实现ServeHTTP接口
func (handler MyHttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("<html><body>Hello, world!</body></html>")) // 将HTML文本写入响应流
}

func main() {
    // 步骤3:将自定义类型对象作为参数调用ListenAndServe()以启动服务
    log.Fatal(http.ListenAndServe("localhost:8000", MyHttpHandler{}))
}

编译、运行程序,在浏览器输入http://localhost:8000/,则会在浏览器中显示"Hello, world!"

很简单吧!不过这里还有两个问题需要解决:

  • 上例中将HTML内容作为硬编码的文本写入响应流,简单的HTML页面固然没问题,但如果是复杂的HTML页面,则很容易产生混乱的代码,而且当需要修改HTML页面内容时,必须重新编译服务器代码,导致灵活性和可维护性低下。如何解决这个问题?
  • 上例中所有URL请求的响应都是一样的,比如在浏览器中输入:http://localhost:8000/index.html,结果仍显示"Hello, world!"。显然实际的服务器不会这么做,实际的服务器会根据不同的URL请求作出不同的响应。那么具体该怎么做呢?

下面分别讨论如何解决这两个问题。

逻辑与视图分离

从文件加载HTML

解决第一个问题的思路很简单,就是将HTML文本移到程序之外。比如放在一个文件中,在处理请求时读取文件并写入到响应流:

greet.html

<html>
    <head></head>
    <body>
        <p>Hello, world!</p>
    </body>
</html>

http2.go

package main

import (
    "io"
    "log"
    "net/http"
    "os"
)

type MyHttpHandler struct{}

func (handler MyHttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    f, err := os.Open("greet.html")
    if err != nil {
        log.Fatalln(err)
    }
    defer f.Close()
    io.Copy(w, f)
}

func main() {
    log.Fatal(http.ListenAndServe("localhost:8000", MyHttpHandler{}))
}

但是这种方式只能处理静态的HTML页面,如何处理动态的HTML页面呢?我们可以使用HTML模板。

从模板加载HTML

关于HTML模板的使用,请移步至我的另一篇博文:Go使用Text和HTML模板

下例输出当前时间到响应流:

time.html

<html>
    <body>
        <p>Now is {{.}}</p>
    </body>
</html>

http3.go

package main

import (
    "html/template"
    "log"
    "net/http"
    "time"
)

type MyHttpHandler struct{}

func (handler MyHttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    t := template.Must(template.ParseFiles("time.html"))
    t.Execute(w, time.Now().Format("2006-01-02 15:04:05"))
}

func main() {
    log.Fatal(http.ListenAndServe("localhost:8000", MyHttpHandler{}))
}

根据不同的请求做出不同的响应

如何根据不同的请求做出不同的响应呢?有以下两种方法。

在Handler中根据URL.Path做出不同的处理

http.Request.URL表示请求的URL,URL.path则表示请求的路径,可以根据这个值来针对不同的请求做出不同的响应:

greet.html

<html>
    <head></head>
    <body>
        <p>Hello, world!</p>
    </body>
</html>

time.html

<html>
    <body>
        <p>Now is {{.}}</p>
    </body>
</html>

http4.go

package main

import (
    "html/template"
    "io"
    "log"
    "net/http"
    "os"
    "time"
)

type MyHttpHandler struct{}

func (handler MyHttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {  // 根据不同路径做出不同响应
    case "/greet":
        f, err := os.Open("greet.html")
        if err != nil {
            log.Fatalln(err)
        }
        defer f.Close()
        io.Copy(w, f)
    case "/time":
        t := template.Must(template.ParseFiles("time.html"))
        t.Execute(w, time.Now().Format("2006-01-02 15:04:05"))
    default:
        w.WriteHeader(http.StatusNotFound)  // 返回404错误
        w.Write([]byte("<html><body>no such page</body></html>"))
    }
}

func main() {
    log.Fatal(http.ListenAndServe("localhost:8000", MyHttpHandler{}))
}

这样一来,在浏览器输入http://localhost:8000/greethttp://localhost:8000/time将得到不同的结果。

利用ServeMux模块化代码

上述方法利用switch-case来处理不同的请求,很显然是不好的做法,当合法的URL请求变得很多时会有很多个case,不利于维护。可以将case里的代码写入函数,但是仍然改变不了switch-case结构。也可以实现一个map,将路径和处理该路径的函数作为键-值对,这是一个很好的方法,不过我们仍然需要实现http.Handler接口,在ServeHTTP()方法中维护这个map,而且每次写Web服务器都需要写这些同样的代码。幸运的是,net/http已经想到了这一点,net/http包提供了ServeMux来完成这个任务,它类似于map,可以将路径和处理该路径的函数关联:

greet.html

<html>
    <head></head>
    <body>
        <p>Hello, world!</p>
    </body>
</html>

time.html

<html>
    <body>
        <p>Now is {{.}}</p>
    </body>
</html>

http5.go

package main

import (
    "html/template"
    "io"
    "log"
    "net/http"
    "os"
    "time"
)

func greet(w http.ResponseWriter, r *http.Request) {
    f, err := os.Open("greet.html")
    if err != nil {
        log.Fatalln(err)
    }
    defer f.Close()
    io.Copy(w, f)
}

func datetime(w http.ResponseWriter, r *http.Request) {
    t := template.Must(template.ParseFiles("time.html"))
    t.Execute(w, time.Now().Format("2006-01-02 15:04:05"))
}

func main() {
    mux := http.NewServeMux()  // 创建一个 ServeMux
    mux.Handle("/greet", http.HandlerFunc(greet))
    mux.Handle("/time", http.HandlerFunc(datetime))
    log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

可以看到,我们不必再自己实现http.Handler接口,只需要提供请求的路径和相应的处理函数就行了。
需要注意的是http.HandlerFunc()并不是一个函数调用,而是一个类型转换:

net/http

package http
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandleFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

可见HandlerFunc是一个适配器,任何能够转换为HandlerFunc的函数在转型后都满足Handler接口。这么设计的目的在于使注册的处理函数必须满足http.Handler接口,这样ServeMux就可以在其内部以ResponseWriter和*Request来调用注册的函数了(听起来有点像C++中的模板,不是吗?)。

考虑到注册函数的通用性,ServeMux提供了一个更便捷的注册方式:

mux.HandleFunc("/greet", greet)
mux.HandleFunc("/time", datetime)

通过ServeMux可以在一个程序中建立多个Web服务器:用不同的ServeMux来注册不同的函数簇,然后分别以这些ServeMux调用ListenAndServe()。不过大多数Web服务器都对应一个程序,net/http考虑到这点,提供了一个全局的ServeMux,http.HandleFunc会将处理函数注册到这个全局的ServeMux,在调用ListenAndServe()时,传递nil作为处理对象,则默认为全局的ServeMux:

func main() {
    http.HandleFunc("/greet", greet)  // 调用全局的 HandleFunc
    http.HandleFunc("/time", datetime)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))  // nil 为处理对象,默认为全局的ServeMux
}

总结

  • net/http提供了搭建Web服务器的一切。
  • 用http.ListenAndServe()创建和启动Web服务器。
  • 实现http.Handler以处理请求。
  • 利用HTML文件和HTML模板将逻辑与视图分离。
  • 利用ServeMux模块化代码。
  • 一个程序只实现一个Web服务器时,利用全局ServeMux简化代码。
版权声明:本文为博主原创文章,转载请注明出处。

go语言 通过http包搭建简单web服务器 对http包源码的略微分析

go语言中http包实现web服务器功能的源码的略微分析

Go Web 基础【2017】01 初窥 HTTP 服务器

  • 2017年03月26日 16:53
  • 108.71MB
  • 下载

使用.net完美解决服务器注销后go的web服务停止的问题

问题描述: 服务器注销后,所有与用户相关的.exe正在文件都会退出,正因为如此,导致了goweb的.exe程序关闭了 解决办法:制作成服务                  网上制作windows服务...

Go源码分析 net/http包分析:追溯到socket

net/http包分析:追溯到socket net/http包分析:追溯到socket完整的代码分析 http.HandleFunc("/", sayhelloName)1.综述2.逐步跳转...

Go网络编程之net/http包执行流程源码分析

1. http包建立web服务器 package main import (     "fmt"     "log"     "net/http"     "stri...
  • huwh_
  • huwh_
  • 2017年07月30日 15:11
  • 377

Go搭建简单服务器及http包源码分析

Go搭建web服务器并对http包源码进行分析
  • Ace_15
  • Ace_15
  • 2017年11月15日 17:42
  • 97

Go搭建第一个web服务器

这里,先上代码,进行演示,然后一一进行论述。 package main import ( "fmt" "log" "net/http" "strings" ) func main() { ht...

快速搭建HTTP服务器 go http server

go http server 例子,自定义路由。 package main import ( "fmt" "net/http" "reflect" "strings"...

Go语言学习之net/http包(The way to go)

生命不止,继续go go go!!!从包名就能看到了吧,是golang中提供http的包: provides HTTP client and server implementations.先看一个...

Subversion服务器搭建(转自:http://blog.csdn.net/ladofwind/archive/2008/02/17/2100200.aspx)

如何快速建立Subversion服务器,并且在项目中使用起来,这是大家最关心的问题,与CVS相比,Subversion有更多的选择,也更加的容易,几个命令就可以建立一套服务器环境,可以使用起来,这里配...
  • lslxdx
  • lslxdx
  • 2011年05月06日 22:06
  • 420
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Go利用net/http包搭建Web服务器
举报原因:
原因补充:

(最多只允许输入30个字)