Go语言学习#12-如何搭建HTTP服务


=Start=

缘由:

学习整理一下如何用Go 语言实现简单的HTTP服务,以便后续参考和使用。

正文:

参考解答:
1、一个超简单的HTTP服务样例
package main

import (
    "fmt"
    "log"
    "net/http"
)

// w 表示response对象,返回给客户端的内容都在对象w里处理
// r 表示客户端请求对象,包含了请求头,请求参数等等
func index(w http.ResponseWriter, r *http.Request) {
    // 往w里写入内容,就会在浏览器里输出
    fmt.Fprintf(w, "Hello golang http!")
}

func main() {
    // 设置路由,如果访问/,则调用index方法
    http.HandleFunc("/", index)

    // 启动web服务,监听9090端口
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        log.Fatal("ListenAndServe:9090", err)
    }
}

保存为 go_http.go 然后用命令 go run go_http.go启动HTTP服务。

2、提供静态文件Web服务
package main

import (
    "log"
    "net/http"
)

func main() {
    // 一行代码实现,在 / 下提供文件服务
    // log.Fatal(http.ListenAndServe(":9090", http.FileServer(http.Dir("/usr/share/doc"))))

    // 或

    // 在指定 URL 下提供文件服务
    // 将本地磁盘中的目录(/usr/share/doc)挂载在URL(/doc/)下面
    // 可以使用 StripPrefix() 方法在 FileServer 看到请求之前修改请求URL的路径
    http.Handle("/doc/", http.StripPrefix("/doc/", http.FileServer(http.Dir("/usr/share/doc"))))

    // 启动web服务,监听9090端口
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}
3、读取HTTP的header/form/body内容的样例
package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

// w 表示response对象,返回给客户端的内容都在对象w里处理
// r 表示客户端请求对象,包含了请求头,请求参数等等
func index(w http.ResponseWriter, r *http.Request) {
    fmt.Printf("Request.Method: %v\n", r.Method)
    
    fmt.Println("Request.Header:")
    for name, value := range r.Header {
        fmt.Printf("%v: %v(%v)", name, value, len(value))
        if len(value) > 1 {
            for idx, value := range value {
                fmt.Printf("\tidx: %v, value: %v\n", idx, value)
            }
        } else {
            fmt.Println()
        }
    }
    fmt.Println()

    r.ParseForm()
    fmt.Println("Request.Form:")
    for name, value := range r.Form {
        fmt.Printf("%v: %v(%v)", name, value, len(value))
        if len(value) > 1 {
            for idx, value := range value {
                fmt.Printf("\tidx: %v, value: %v\n", idx, value)
            }
        } else {
            fmt.Println()
        }
    }
    fmt.Println()

    s, _ := ioutil.ReadAll(r.Body) //把 body 内容读入字符串 s
    fmt.Printf("Request.Body: %v\n", string(s))
    fmt.Println()

    // 往w里写入内容,就会在浏览器里输出
    fmt.Fprintf(w, "Hello golang http!")
}

func main() {
    // 简单的静态Web服务器
    // log.Fatal(http.ListenAndServe(":9090", http.FileServer(http.Dir("/usr/share/doc"))))

    // // 设置路由,如果访问/,则调用index方法
    http.HandleFunc("/", index)

    // 将本地磁盘中的目录(/tmp)挂载在URL(/tmpfiles/)下面
    // 可以使用 StripPrefix() 方法在 FileServer 看到之前修改请求URL的路径
    http.Handle("/tmpfiles/", http.StripPrefix("/tmpfiles/", http.FileServer(http.Dir("/tmp"))))

    // 启动web服务,监听9090端口
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

主要起演示作用,如果想知道更多http.Request和http.ResponseWriter有哪些字段,可以达到什么功能,可以去godoc里面看,非常方便和简单。

type Request struct {
    Method string

    URL *url.URL

    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0

    //	Header = map[string][]string{
    //		"Accept-Encoding": {"gzip, deflate"},
    //		"Accept-Language": {"en-us"},
    //		"Foo": {"Bar", "two"},
    //	}
    Header Header

    // Body is the request's body.
    //
    // For client requests a nil body means the request has no
    // body, such as a GET request. The HTTP Client's Transport
    // is responsible for calling the Close method.
    //
    // For server requests the Request Body is always non-nil
    // but will return EOF immediately when no body is present.
    // The Server will close the request body. The ServeHTTP
    // Handler does not need to.
    Body io.ReadCloser

    // For server requests it is unused.
    GetBody func() (io.ReadCloser, error)

    ContentLength int64

    TransferEncoding []string

    Close bool

    Host string

    // Form contains the parsed form data, including both the URL
    // field's query parameters and the POST or PUT form data.
    // This field is only available after ParseForm is called.
    // The HTTP client ignores Form and uses Body instead.
    Form url.Values

    // PostForm contains the parsed form data from POST, PATCH,
    // or PUT body parameters.
    // This field is only available after ParseForm is called.
    // The HTTP client ignores PostForm and uses Body instead.
    PostForm url.Values

    // MultipartForm is the parsed multipart form, including file uploads.
    // This field is only available after ParseMultipartForm is called.
    // The HTTP client ignores MultipartForm and uses Body instead.
    MultipartForm *multipart.Form

    // Few HTTP clients, servers, or proxies support HTTP trailers.
    Trailer Header

    // This field is ignored by the HTTP client.
    RemoteAddr string

    RequestURI string

    TLS *tls.ConnectionState

    Cancel <-chan struct{}

    Response *Response
    // contains filtered or unexported fields
}

&

// url.Values
type Values map[string][]string

// url.URL
type URL struct {
    Scheme     string
    Opaque     string    // encoded opaque data
    User       *Userinfo // username and password information
    Host       string    // host or host:port
    Path       string
    RawPath    string // encoded path hint (Go 1.5 and later only; see EscapedPath method)
    ForceQuery bool   // append a query ('?') even if RawQuery is empty
    RawQuery   string // encoded query values, without '?'
    Fragment   string // fragment for references, without '#'
}

 

参考链接:

=END=

, ,

《 “Go语言学习#12-如何搭建HTTP服务” 》 有 3 条评论

  1. 【Go】优雅的读取http请求或响应的数据
    https://blog.thinkeridea.com/201901/go/you_ya_de_du_qu_http_qing_qiu_huo_xiang_ying_de_shu_ju.html
    `
    背景介绍
    我们有许多 api 服务,全部采用 json 数据格式,请求体就是整个 json 字符串,当一个请求到服务端会经过一些业务处理,然后再请求后面更多的服务,所有的服务之间都用 http 协议来通信(啊, 为啥不用 RPC,因为所有的服务都会对第三方开放,http + json 更好对接),大多数请求数据大小在 1K~4K,响应的数据在 1K~8K,早期所有的服务都使用 ioutil.ReadAll 来读取数据,随着流量增加使用 pprof 来分析发现 bytes.makeSlice 总是排在第一,并且占用了整个程序 1/10 的内存分配,我决定针对这个问题进行优化,下面是整个优化过程的记录。

    使用合适大小的 buffer 来减少内存分配,sync.Pool 可以帮助复用 buffer, 一定要自己写这些逻辑,避免使用三方包,三方包即使使用同样的技巧为了避免数据争用,在返回数据时候必然会拷贝一个新的数据返回,就像 jsoniter 虽然使用了 sync.Pool 和 buffer 但是返回数据时还需要拷贝,另外这种通用包并不能给一个非常贴合业务的初始 buffer 大小,过小会导致数据发生拷贝,过大会太过浪费内存。

    程序中善用 buffer 和 sync.Pool 可以大大的改善程序的性能,并且这两个组合在一起使用非常的简单,并不会使代码变的复杂。
    `

  2. zinx:基于 Golang 的轻量级 TCP 并发服务器框架
    https://paper.tuisec.win/detail/224288f119a7a23
    https://toutiao.io/posts/zn5zda/preview
    https://www.jianshu.com/p/23d07c0a28e5

    基于Golang解决的长连接并发服务器框架
    https://github.com/aceld/zinx
    `
    Zinx源代码
    完整教程电子版(在线高清)-下载
    Zinx框架视频教程(框架篇)(完整版下载)链接在下面正文
    Zinx框架视频教程(应用篇)(完整版下载)链接在下面正文
    Zinx开发API文档
    Zinx第一章-引言
    Zinx第二章-初识Zinx框架
    Zinx第三章-基础路由模块
    Zinx第四章-全局配置
    Zinx第五章-消息封装
    Zinx第六章-多路由模式
    Zinx第七章-读写分离模型
    Zinx第八章-消息队列及多任务
    Zinx第九章-链接管理
    Zinx第十章-连接属性设置
    `

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注