0%

go_web(1)

摘要

介绍使用Go开发web应用的方法,并阐明Go的相对优势;

对HTTP协议等构成Web应用的关键概念做深入浅出的介绍。

1.1 Go编写Web应用的优势

  • 可扩展:管理者可以通过添加更多的硬件来获得更强的请求处理能力,go中表现为高并发(垂直扩展)、架设代理部署web应用(水平扩展)
  • 模块化:大规模web应用应由可替换的组件构成,这使得开发者能更系统的分工、开发并减少人力、脑力支出。go中表现为通过它的接口机制对行为进行描述,一次来实现动态类型匹配
  • 可维护:拥有一个易于维护的代码库(code-base)。go中表现为其拥有简介且极具可读性的语法以及灵活且清晰的包管理系统、文档工具go-doc、测试工具go-test等。
  • 高性能:不仅意味着短时间可以处理带量请求,还意味着服务器能快速地对客户端进行响应,并让终端用户(end user)能快速地执行操作。go中表现为go-routine并发

1.2 Web应用的工作原理

Web应用的定义(狭义上):会对客户端发送的HTTP请求做出想用,并通过HTTP响应将HTML回传到客户端。

换句话说,一个程序只需满足一下两个条件,我们就可以把它看做是一个web应用:

  • 这个程序必须向发送命令请求的客户端返回HTML,而客户端则会向用户展示渲染后的HTML;
  • 这个程序在向客户端传送数据是必须使用HTTP协议。

所以,如果一个程序不是向客户端渲染并展示HTML,而是向其他程序返回某种非HTML格式的数据,那么这个程序就是一个为其他程序提供服务的Web服务

1.3 HTTP简介

HTTP:万维网的应用层通信协议,Web页面中的所有数据都是通过这个看似简单的文本协议进行传输的。

HTTP版本有很多,最初版本为HTTP 0.9, HTTP 1.0由大量特性合并而成,HTTP 1.1是目前使用最广泛的版本。这里主要对HTTP 1.1进行讨论,但也适当介绍HTTP 2.0的相关信息。

HTTP定义:是一种无状态由文本构成请求-响应(request-response)协议,这种协议使用的是客户端-服务器(client-server)计算模型。

01

客户端向服务器发起对话,服务器为客户端提供服务

在HTTP协议中,客户端也被称为用户代理(user-agent),而服务器通常被称为Web服务器

大多数情况下,HTTP客户端都是一个Web浏览器

HTTP是一种无状态协议,它唯一知道的就是客户端会向服务器发送请求,而服务器则会向客户端返回响应,并且后续发生的请求对之前发生过的请求一无所知,即不会在服务器和客户端见创建一个持续存在的通信通道。但如今HTTP 1.1也可以通过持久化连接来提升性能。

HTTP以纯文本方式而不是二进制方式发送和接收协议数据。这样做是为了让开发者可以在无需使用专门的协议分析工具的情况下,你搞清楚通信中正在发生的事情,从而更容易进行故障排查。

1.4 HTTP请求

HTTP请求跟其他所有HTTP报文(message)一样,都由一系列文本行组成,这些文本行会按照以下顺序进行排列:

  1. 请求行(request-line)
  2. 零个或任意多个请求首部(header)
  3. 一个空行
  4. 可选的报文主体(body)

一个典型的HTTP请求看上去是这样的:

1
2
3
4
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1  #请求行
Host: www.w3.org
User-Agent: Mozilla/5.0 #首部
(empty line) #空行+主体

请求行中的第一个单词为请求方法(request method),之后跟着的是统一资源标识符(Uniform Resource Identifier,URI)以及所用的HTTP版本。

位于请求行之后的两个文本行为请求的首部(header)。

这个报文的最后一行为行,技术报文主体内容为空,这个空行也必须存在,至于报文是否包含主体则需要根据请求使用的方法而定。

1.4.1 请求方法

请求方法指明了客户端想要对资源执行的操作。

各个HTTP方法的作用说明如下:

  • GET——命令服务器返回指定的资源
  • HEAD——与GET方法作用类似,唯一不同在于它不要求服务器返回报文的主体。通常用于在不获取报文主体的情况下,取得响应的首部
  • POST——命令服务器将报文主体中的数据传递给URI指定的资源,至于服务器具体会对这些数据执行什么动作则取决于服务器本身。
  • PUT——命令服务器将报文主体中的数据设置为URI指定的资源。如果URI指定的位置上已有数据存在,那么使用报文主体中的数据去代替已有的数据。如果资源尚未存在,那么在URI指定的位置上新创建一个资源。
  • DELETE——命令服务器删除URI指定的资源。
  • TRACE——命令服务器返回请求本身。通过这个方法,客户端可以知道介于它和服务器之间的其他服务器是如何处理请求的。
  • OPTIONS——命令服务器发返回它支持的HTTP方法列表
  • CONNECT——命令服务器和客户端之间建立一个网络连接。这个方法通常用于设置SSL隧道以开启HTTPS功能。
  • PATCH——命令服务器使用报文主体中的数据对URI指定的资源进行修改

1.4.2 安全的请求方法

如果一个HTTP方法只要求服务器提供信息而不会对服务器的状态做任何修改,呢么这个方法就是安全的(safe)。

安全的方法:GET、HEAD、OPTIONS、TRACE;

不安全的方法:POST、PUT、DELETE 。

1.4.3 幂等的请求方法

如果一个HTTP方法在使用相同的数据进行第二次调用的时候,不会对服务器的状态造成任何改变,那么这个方法就是幂等的(idempotent)。

安全的方法天生就是幂等的。

PUT、DELETE虽然不安全,但却是幂等的。

POST既不安全也不幂等(因为重复的POST请求是否会改变服务器状态是由服务器本身决定的)。

1.4.4 浏览器对请求方法的支持

HTML不支持除GET和POST之外的其他HTTP方法。

1.4.5请求首部

HTTP请求方法定义了发送请求的客户端想要执行的操作,而HTTP请求的首部则记录了与请求本身以及客户端有关的信息

组成:由任意多个用冒号分隔的纯文本键值对组成,最后以回车(CR)和换行(LF)结尾。

宿主(Host)首部字段是HTTP 1.1 唯一强制要求的首部

如果请求的报文中包含有可选的主体,那么请求的首部还需要带有内容长度(Content-Length)字段或者传输编码(Transfer-Encoding)字段。

首部字段 作用描述
Accept 客户端在HTTP响应中能够接收的内容类型(Accept: text/html)(接收文本文件)
Accept-Charset 客户端要求服务器使用的字符集编码(Accept-Charset: utf-8)
Authorization 向服务器发送基本的身份验证证书
Cookie 客户端将这个首部中服务器之前设置的所有cookie回传给服务器
Content-Length 请求主体的字节长度
Content-Type 当请求包含主体的时候,这个首部用于记录主体内容的类型
Host 服务器的名字以及端口号,如果这个首部没有记录服务器的端口号,就表示服务器使用的是80端口
Referrer 发起请求的页面所在的地址
Server 说明服务器名称
User-Agent 对发起请求的客户端进行描述

1.5 HTTP响应

HTTP响应是对HTTP请求报文的回复。

HTTP响应构成:

  • 一个状态行
  • 零个或任意几行的响应首部
  • 一个空行
  • 一个可选的报文主体

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/1.1 200 OK
Date: Sat, 17 Jul 2021 10:47:13 GMT
Server: Apache/2
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Content-Length: 51
Content-Type: text/plain;charset=iso-8859-1

<html>
<head>
<title>polaris6g homepage</title>
</head>
<body>
<!--body goes here-->
</body>
</html>

HTTP响应的第一行为HTTP版本、状态行(status code)和相应的原因短语(reason phrase),原因短语对状态码进行了简单的描述。

这个栗子还包含了一个HTML格式的报文主体。

1.5.1 响应状态码

HTTP响应中的状态码表明了响应的类型。

HTTP响应状态码共有5种,以不同的数字作为前缀。

状态码类型 作用描述
1XX 情报状态码
2xx 成功状态码
3xx 重定向状态码
4xx 客户端错误状态码
5xx 服务器错误状态码
  • 1XX:服务器通过这些状态码来告知客户端,自己已经接受到了客户端的请求,并已经对请求进行了处理
  • 2XX:已接受请求+成功处理;标准响应: “200 OK”
  • 3XX:已接受请求+成功处理;但为了完成请求指定的动作,客户端还需要再做一些其他工作。大多用于实现URL重定向
  • 4XX:客户端发送的请求出现问题。最常见:”404 Not Found”(服务器无法从请求指定的URL中找到客户端想要的资源)
  • 5XX:服务器因为某些原因而无法正确处理请求。最常见:”500 Internet Server Error”

1.5.2 响应首部

首部字段 作用描述
Allow 告知客户端,服务器支持哪些请求方法
Content-Length 响应主体的字节长度
Content-Type 如果响应包含可选的主体,首部记录主题内容类型
Data 以格林尼治标准时间(GMT)可是记录当前时间
Location 仅在重定向时使用,会告知客户端接下来应该向哪个URL发送请求
Server 返回响应服务器的域名
Set-Cookie 在客户端里设置一个cookie。一个响应里可以包含多个首部
WWW-Authenticate 服务器通过这个首部告知客户端,在Authorization请求首部中应该提供哪种类型的身份验证信息

1.6 URI

统一资源标识符(Uniform Resource Identifier, URI)

它包含了统一资源名称(URN)和统一资源定位符(URL),且这两者有相似的语法和格式。

URI的一般格式:

1
<方案名称>:<分层部分>[ ? <查询参数> ] [ # <片段> ]
  • 方案名称:记录了URI正在使用的方案,它定义了URI其余部分的结构。多数情况会使用HTTP方案。
  • 分层部分:包含了资源的识别信息,这些信息回忆分层的方式进行组织。若以(//)开头,那么说明它包含了可选的用户信息,这些信息将以@符号结尾,后跟分层路径。不带用户信息的分层部分就是一个单纯的路径,每个路径由一连串的分段(segment)组成,各个分段之间使用单斜线(/)分隔。

方案名称和分层部分是URI中必需的。

  • 查询参数(query):查询参数是可选的,这些参数用于包含无法使用分层方式表示其他信息。多个查询参数会被阻滞成一连串的键值对,各个键值对之间使用&符号分隔。
  • 片段(fragment):可以对URI定义的资源中的次级资源(secondary resource)进行表示。

因为每个URL都是一个单独的字符串,所以URL里面不能够包含空格。

URL编码:URL编码会把保留字符转换成改字符在ASCⅡ编码中对应的字节值,接着把这个字节值表示为一个两位长的 十六进制数字,最后再这个十六进制数字的前面加上一个百分号(%)。

序号 特殊字符 含义 十六进制值
1. + URL 中+号表示空格 %2B
2. 空格 URL中的空格可以用+号或者编码 %20
3. / 分隔目录和子目录 %2F
4. ? 分隔实际的 URL 和参数 %3F
5. % 指定特殊字符 %25
6. # 表示书签 %23
7. & URL 中指定的参数间的分隔符 %26
8. = URL 中指定参数的值 %3D

1.7 HTTP/2 简介

  • HTTP/2是一种二进制协议,与纯文本方式表示的HTTP 1.x不同,这使得HTTP/2的语法分析更高效,协议更为紧凑和健壮。
  • HTTP/2是完全多路复用(fully multiplexed)的,这意味着多个请求和响应可以在同一时间内使用同一个连接。
  • HTTP/2对首部进行压缩以减少需要传送的数据量。

1.8 Web应用的各个组成部分

Web应用就是执行以下任务的程序:

  1. 通过HTTP协议,以HTTP请求报文的形式获取客户端输入;
  2. 对HTTP请求报文进行处理,并执行必要的操作;
  3. 生成HTML,并以HTTP响应报文的形式将其返回给客户端。

为了完成这些任务,Web应用被分成了处理器(handler)模板引擎(template engine)这两个部分。

1.8.1 处理器

处理器工作:

  1. 接受和处理客户端发来的请求;
  2. 调用模板引擎;
  3. 将返回数据填充至回传给客户端的响应报文中;

1.8.2 模板引擎

模板引擎通过模板和数据来生成最终的HTML,这些HTML之后会作为HTTP响应的其中一部分被回传至客户端。

模板可分为静态模板和动态模板:

  • 静态模板:夹杂着一些占位符的HTML,静态模板引擎通过将占位符替换成响应的数据来生成最终的HTML。
  • 动态模板:除了包含HTML、占位符之外,还包含一些编程语言结构,如条件语句、迭代语句和变量。

1.9 Hello Go

实例使用Go语言构建Web应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter,r *http.Request){//responsewriter接口,指向request结构的指针
fmt.Fprintf(w,"Hello World, %s!",r.URL.Path[1:])//三个参数,最后一个提取request结构里的路径信息
}

func main(){
http.HandleFunc("/ping",handler)//把handler函数设置成URL(/ping)被访问时的处理器
http.ListenAndServe(":8080",nil)//启动服务器监听系统的8080端口
}