关闭

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

标签: Goweb开发web服务器标准包http
1224人阅读 评论(0) 收藏 举报
分类:

说明

本文介绍了利用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简化代码。
1
0
查看评论

如何使用Docker部署Go Web应用程序

您将通过本文了解如何使用Docker部署Go Web应用程序,以及Docker如何帮您改善开发工作流和部署过程。各种规模的团队都能从本文内容中获益。
  • horsefoot
  • horsefoot
  • 2016-06-13 08:54
  • 15459

go语言之http服务器的简单建立

go语言之http服务器的简单建立 先导入net/http包 import (     "net/http" ) 然后再添加2行代码就可以了。 func main() {    ...
  • bojie5744
  • bojie5744
  • 2015-02-11 08:54
  • 2053

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

简介 利用Go的标准包net/http可以很方便的搭建服务器。实际上只需要一个函数和一个接口: net/http−−−−−−−\underline{net/http} package http // 建立服务器,address为服务器地址,比如:"localhost:8000&qu...
  • u011304970
  • u011304970
  • 2017-05-26 12:39
  • 1224

使用Golang 搭建http web服务器

这篇文章出现的理由是业务上需要创建一个Web Server。创建web是所有语言出现必须实现的功能之一了。在nginx+fastcgi+php广为使用的今天,这里我们不妨使用Go来进行web服务器的搭建。 前言 使用Go搭建Web服务器的包有很多,大致有下面几种方法,直接使用net包,使用net...
  • ivy19860929
  • ivy19860929
  • 2016-04-08 17:04
  • 2825

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

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

golang 建立web服务器 http包源码详解

golang 建立web服务器 http包源码详解首先,熟悉http协议的都知道,http协议是基于TCP实现的。http服务器的工作方式大概就是监听socket端口,接受连接,获取到请求,处理请求,返回响应。所以,对应的会有几个部分 Request:用户请求的信息。post、get、url等这些信...
  • qq_24850089
  • qq_24850089
  • 2017-02-16 04:21
  • 1349

go搭建一个简单web服务器

Go语言里面提供了一个完善的net/http包,通过http包可以很 方便的就搭建起来一个可以运行的web服务。同时使用这个包能很简单地对web的路由,静态文件,模版,cookie等数 据进行设置和操作。 http包建立web服务器 1、服务端 server.go package main i...
  • nuli888
  • nuli888
  • 2017-03-18 20:55
  • 1449

Go Web编程:http包分析

一个简单的web服务package mainimport ( "io" "log" "net/http" )func HelloGoServer(w http.ResponseWriter, req *http.Re...
  • uudou
  • uudou
  • 2016-10-05 12:22
  • 2511

Go net/http包

使用net/http包快速创建一个HTTP服务器。// file http_test1.go package main import ( "fmt" "net/http" "log" )func HandleInde...
  • cc7756789w
  • cc7756789w
  • 2016-03-29 19:31
  • 440

Go net/http 超时机制完全手册

原文链接:http://colobu.com/2016/07/01/the-complete-guide-to-golang-net-http-timeouts/ 英文原始出处: The complete guide to Go net/http timeouts , ...
  • u013870094
  • u013870094
  • 2017-11-10 21:03
  • 197
    个人资料
    • 访问:97723次
    • 积分:2577
    • 等级:
    • 排名:第16771名
    • 原创:130篇
    • 转载:0篇
    • 译文:23篇
    • 评论:16条
    相关连接
    博客专栏
    文章分类
    最新评论