第一种:最简单的

package main

import (
"fmt"
"log"
"net/http"
) func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello http server!")
} func main() {
http.HandleFunc("/", myHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}

上面的变种1:

//上面的一个变种
package main import (
"fmt"
"log"
"net/http"
) func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello http server 1!")
} func main() {
//// 通过 HandlerFunc 把函数转换成 Handler 接口的实现对象
handler := http.HandlerFunc(myHandler)
http.Handle("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}

ListenAndServe函数负责监听并处理连接。

第二种:Handler接口

上面的那种方式发挥余地太小,比如我想设置server的Timeout时间都不能设置。这时候我们就用到了 自定义的server

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了。示例代码如下

package main

import (
"fmt"
"log"
"net/http"
) type myHandler struct{} func (myHandler myHandler) ServeHTTP(w ResponseWrite, r *Request) {
fmt.Fprintf(w, "hello serverhttp 1")
} func main() {
server := http.Server{
Addr: ":8080",
Handler: &myHandler{},
ReadTimeout: 3 * time.Second,
}
log.Fatal(server.ListenAndServe(":8080", nil))
}

还有下面这种:

package main

import (
"fmt"
"log"
"net/http"
) type myHandler struct{} func (myHandler *myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello, server 2!")
} func main() {
http.Handle("/", &myHandler{})
log.Fatal(http.ListenAndServe(":8080", nil))
}

还有这种:

package main

import (
"fmt"
"log"
"net/http"
) type MyHandler struct{} func (myHandler *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
case "/hello":
for k, v := range r.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
default:
fmt.Fprintf(w, "404 not found: %s\n", r.URL)
}
} func main() {
myHandler := new(MyHandler)
log.Fatal(http.ListenAndServe(":8080", myHandler))
}

第三种:多个url的ServeMux

ServeMux可以注册多个URL和handler的对应关系,并自动把请求转移到对应的handler进行处理。

package main

import (
"fmt"
"log"
"net/http"
) func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello server!")
} func yourHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "your handler")
} func main() {
mux := http.NewServeMux()
mux.HandleFunc("/my", myHandler)
mux.HandleFunc("/your", yourHandler) log.Fatal(http.ListenAndServe(":8080", mux))
}
  • 通过 NewServeMux 生成了 ServerMux 结构,URL 和 handler 是通过它注册的
  • http.ListenAndServe 方法第二个参数变成了上面的 mux 变量

思考:上面写的那些程序是怎么实现的?

一个HTTP请求过程要认识几个概念,服务器端的几个重要概念:

  • Request: 用户请求的信息,这个可以用来处理用户的请求信息,POST,url,cookie,header等信息
  • Response:服务器返回给用户的信息
  • Conn:每次连接的信息
  • Handler:处理请求和生成返回处理信息的逻辑

url路由到处理函数是怎么实现的?就是上面所说的Handler的处理逻辑。

我们先来看看golang自带的包 net/http 下的程序,主要程序在 net/http/server.go 文件中。

经过追踪程序,去掉其他的细节末节,处理url到handler的一个最重要函数是 func (mux *ServeMux) Handle(pattern string, handler Handler),即使是func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))这个处理函数,最后也是Handle函数来处理的,看看它们具体的代码:

//具体处理url路由到Handler
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }
    if pattern[0] != '/' {
        mux.hosts = true
    }
} // HandleFunc registers the handler function for the given pattern.
//路由url到HandleFunc,与上面的函数(func (mux *ServeMux) Handle(pattern string, handler Handler))有什么区别?
//这个HandleFunc是具体的处理函数,而上面的Handle函数是一个接口,一个类型只要实现接口里面的方法,那么就可以传入进来作为Handler进行处理
//我的理解就是提供了2中不同的处理url路由的方法
//其实这个函数最后处理还是用了上面的Handle()方法
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

有了处理函数,肯定会有另外一个疑问,url是怎么映射到处理函数的呢?其实我们从上面方法就可以看出来,方法有一个接收体 mux *ServeMux ,看看这个具体代码是什么?

// 一个结构体,存储了url,也存储了对于的Handler,在muxEntry里
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    es    []muxEntry // slice of entries sorted from longest to shortest.
    hosts bool       // whether any patterns contain hostnames
} // 定义了存储的路由和根据路由来处理逻辑的Handler
type muxEntry struct {
    h       Handler //根据路由具体的处理接口
    pattern string //定义的路由
} // DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

上面的muxEntry struct 结构体有一个h字段,他是 Handler接口,它具体是什么呢?

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

它是处理HTTP请求的一个接口。这也是设计好程序的一个方法,依赖接口,而不是具体的实现。

也就是说只要实现了这个接口里的方法ServeHTTP(ResponseWriter, *Request),就是处理HTTP请求了。

包里面就有一个这样的程序:

//装饰器模式
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r). 实现了 Handler interface里的方法
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

包里还定义了默认处理方法,如下

func Handle(pattern string, handler Handler) { 
DefaultServeMux.Handle(pattern, handler) 
} func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

其实我们看看上面的方法,是不是调用的最开始我们分析的DefaultServeMux.Handle(pattern, handler)DefaultServeMux.HandleFunc(pattern, handler)

2个处理函数 HandleHandleFunc

包里有默认处理函数,那我们能不能自定义函数处理呢?

答案:当然可以的。你看 第二种:Handler接口 就是自定义的处理函数,因为我们定义一个类型struct,然后它实现了Handler 接口方法 ServeHTTP , 而处理url路由到Handler的方法第二个参数就是Handler接口,

看看方法:func (mux *ServeMux) Handle(pattern string, handler Handler)。 所以当然是可以的。

这也是一个依赖接口设计的好处,不仅官方包可以写处理函数,我们也可以自定义处理函数。但是Handle函数参数不用修改。只要传一个实现了接口方法的类型数据就可以了。

update: 2020.01.20 晚

go http server 编程的更多相关文章

  1. Oracle与SQL SERVER编程差异分析(入门)

    网上有关Oracle与SQL SERVER性能差异的文章很多,结论往往是让你根据数据量与预算来选择数据库.但实际项目中,特别是使用 .Net 开发的系统,支持以上两种数据库或者更多已经成为Boss的普 ...

  2. 跨平台网络通信与server编程框架库(acl库)介绍

    一.描写叙述 acl project是一个跨平台(支持LINUX,WIN32,Solaris,MacOS,FreeBSD)的网络通信库及server编程框架,同一时候提供很多其它的有用功能库.通过该库 ...

  3. SQL Server编程系列(2):SMO常用对象的有关操作

    原文:SQL Server编程系列(2):SMO常用对象的有关操作 在上一篇周公简单讲述了SMO的一些基本概念,实际上SMO体系结构远不止周公在上一篇中讲述的那么简单,下图是MSDN上给出的一个完整的 ...

  4. SQL Server编程系列(1):SMO介绍

    原文:SQL Server编程系列(1):SMO介绍 续篇:SQL Server编程系列(2):SMO常用对象的有关操作 最近在项目中用到了有关SQL Server管理任务方面的编程实现,有了一些自己 ...

  5. (转) SQL Server编程系列(1):SMO介绍

    最近在项目中用到了有关SQL Server管理任务方面的编程实现,有了一些自己的心得体会,想在此跟大家分享一下,在工作中用到了SMO/SQL CLR/SSIS等方面的知识,在国内这方面的文章并不多见, ...

  6. [转]SQL Server编程:SMO介绍

    转自:周公 最近在项目中用到了有关SQL Server管理任务方面的编程实现,有了一些自己的心得体会,想在此跟大家分享一下,在工作中用到了SMO/SQL CLR/SSIS等方面的知识,在国内这方面的文 ...

  7. SQL Server编程入门

    SQL编程要比Java编程.C#编程简单许多,下面我们直接讲干货21:04:31 使用变量 局部变量 在T-SQL中,局部变量的名称必须以标记@作为前缀.T-SQL的局部变量其实和Java中的局部变量 ...

  8. SQL Server编程(02)自定义函数

    在编程过程中,我们通常把特定的功能语句块封装称函数,方便代码的重用.我们可以在SQL Server中自定义函数,根据函数返回值的区别,我们自定义的函数分两种:标量值函数和表值函数. 自定义函数的优点: ...

  9. Linux高性能server编程——Linux网络基础API及应用

     Linux网络编程基础API 具体介绍了socket地址意义极其API,在介绍数据读写API部分引入一个有关带外数据发送和接收的程序,最后还介绍了其它一些辅助API. socket地址API 主 ...

  10. Linux高性能server编程——信号及应用

     信号 信号是由用户.系统或者进程发送给目标进程的信息.以通知目标进程某个状态的改变或系统异常. Linux信号可由例如以下条件产生: 对于前台进程.用户能够通过输入特殊的终端字符来给它发送信号. ...

随机推荐

  1. hive数据仓库表设计之(矮宽表+高窄表)

    昨天面对某客户域做表关联的时候发现了. 有两张相同内容的主表.但是表的设计结构并不相同: (每个领域都有主表,每次往这个领域(库)添加新表的时候一般都会join 主表,从而有唯一的主键id) 这两个表 ...

  2. e.target.value和this的区别

    1.e.target.value获取的就是你选择接受事件的元素输入的或者选择的值. 参数e接收事件对象. 而事件对象也有很多属性和方法,其中target属性是获取触发事件对象的目标,也就是绑定事件的元 ...

  3. Spring Boot 跨域访问

    如何在 Spring Boot 中配置跨域访问呢? Spring Boot 提供了对 CORS 的支持,您可以实现WebMvcConfigurer 接口,重写addCorsMappings 方法来添加 ...

  4. 对JavaScript 模块化的深入-----------------引用

     什么是模块化 好的代码模块分割的内容一定是很合理的,便于你增加减少或者修改功能,同时又不会影响整个系统.  为什么要使用模块 1.可维护性:根据定义,每个模块都是独立的.良好设计的模块会尽量与外部的 ...

  5. [引用]MATLAB中的fft后为何要用fftshift

    原文地址:MATLAB中的fft后为何要用fftshift fft是一维傅里叶变换,即将时域信号转换为频域. fftshift是针对频域的,将FFT的DC分量移到频谱中心,重新排列fft,fft1和… ...

  6. JVM(十二),垃圾回收面试题

    十二.垃圾回收面试题 1.Object的finalize()方法 2.Java中的强软弱虚四种引用 (1)强引用 (2)软引用(间接引用) (3)弱引用 (4)虚引用 (5)四种引用区别

  7. shell基础之二 bash特性详解

    https://blog.51cto.com/13520779/2093146 合格linux运维人员必会的30道shell编程面试题及讲解:https://blog.51cto.com/oldboy ...

  8. Codeforces 955C Sad powers(数论)

    Codeforces 955C Sad powers 题意 q组询问,每次询问给定L,R,求[L,R]区间内有多少个数可以写成ap的形式,其中a>0,p>1,1 ≤ L ≤ R ≤ 1e1 ...

  9. jsp页面,使用Struts2标签,传递和获取Action类里的参数,注意事项。<s:a action><s:iterator><s:param>ognl表达式

    在编写SSH2项目的时候,除了使用<s:form>表单标签向Action类跳转并传递参数之外,很更多时候还需要用到<s:a action="XXX.action" ...

  10. 获取当前页面的webview ID

    代码: A页面 <script type="text/javascript"> var ws = null; mui.plusReady(function(){ ws ...