Go web 基础相关知识

03-27 阅读 0评论

Go web

Web工作方式

浏览器本身是一个客户端,当你输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务

Go web 基础相关知识,Go web 基础相关知识,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,使用,我们,访问,第1张
(图片来源网络,侵删)

Go web 基础相关知识

URL和DNS解析

URL是“统一资源定位符”的英文缩写,用于描述一个网络上的资源

scheme://host[:port#]/path/.../[?query-string][#anchor]
scheme         指定低层使用的协议(例如:http, https, ftp)
host           HTTP服务器的IP地址或者域名
port#          HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/
path           访问资源的路径
query-string   发送给http服务器的数据
anchor         锚

DNS 是**“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,他用于TCP/IP网络,从事将主机名或域名转换为实际IP地址**的工作

Go web 基础相关知识

DNS解析过程:

1.在浏览器中输入www.qq.com域名,操作系统会检查自己本地的hosts文件是否由这个网址映射关系,如果有,直接返回域名解析

Go web 基础相关知识,Go web 基础相关知识,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,使用,我们,访问,第4张
(图片来源网络,侵删)

2.如果hosts里没有这个域名的映射,则查找本地DNS解析器缓存,是否有这个网址映射关系

3.如果本地DNS缓存器缓存都没有相应的网址映射关系,首先会找TCP/IP参数中设置的首选DNS服务器,在此我们叫他本地DNS服务器,如果要查询的域名,包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析

4.如果要查询的域名,不由本地DNS服务器区域解析

Go web 基础相关知识

举个例子来说,你想知道某个一起上法律课的女孩的电话,并且你偷偷拍了她的照片,回到寝室告诉一个很仗义的哥们儿,这个哥们儿二话没说,拍着胸脯告诉你,甭急,我替你查(此处完成了一次递归查询,即,问询者的角色更替)。然后他拿着照片问了学院大四学长,学长告诉他,这姑娘是xx系的;然后这哥们儿马不停蹄又问了xx系的办公室主任助理同学,助理同学说是xx系yy班的,然后很仗义的哥们儿去xx系yy班的班长那里取到了该女孩儿电话。(此处完成若干次迭代查询,即,问询者角色不变,但反复更替问询对象)最后,他把号码交到了你手里。完成整个查询过程

HTTP协议详解

HTTP协议是Web工作的核心

Go web 基础相关知识,Go web 基础相关知识,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,使用,我们,访问,第6张
(图片来源网络,侵删)

HTTP是一种让Web服务器与浏览器通过Internet发送与接收数据的协议,它建立在TCP协议之上,一般采用TCP的80端口。

HTTP协议是无状态的,同一个客户端的这次请求和上次请求时没有对应关系,对HTTP服务器来说,它并不知道这两个请求

HTTP请求包(浏览器信息)

GET /domains/example/ HTTP/1.1        //请求行: 请求方法 请求URI HTTP协议/协议版本
Host:www.iana.org                //服务端的主机名
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4            //浏览器信息
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8    //客户端能接收的mine
Accept-Encoding:gzip,deflate,sdch        //是否支持流压缩
Accept-Charset:UTF-8,*;q=0.5        //客户端字符编码集
//空行,用于分割请求头和消息体
//消息体,请求资源参数,例如POST传递的参数

HTTP协议定义了很多与服务器交互的请求方法,最基本的有4种,分别是GET,POST,PUT,DELETE。一个URL地址用于描述一个网络上的资源,而HTTP中的GET, POST, PUT, DELETE就对应着对这个资源的查,增,改,删4个操作。我们最常见的就是GET和POST了。GET一般用于获取/查询资源信息,而POST一般用于更新资源信息。

Go web 基础相关知识

这个是GET信息

Go web 基础相关知识

这个是POST信息

GET和POST信息:

1.GET请求信息体为空,POST请求带有消息体

2.GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连

3.GET提交的数据大小有限制,而POST方法提交的数据没有限制

4.GET方式提交数据,会带来安全问题

如果输入用户名和密码提交数据,用户名和密码将出现在URL上,如果页面可以被缓存,将从历史记录获得该用户的账号和密码

HTTP响应包(服务器信息)

response包:

HTTP/1.1 200 OK                        //状态行
Server: nginx/1.0.8                    //服务器使用的WEB软件名及版本
Date:Date: Tue, 30 Oct 2012 04:14:25 GMT        //发送时间
Content-Type: text/html                //服务器发送信息的类型
Transfer-Encoding: chunked            //表示发送HTTP包是分段发的
Connection: keep-alive                //保持连接状态
Content-Length: 90                    //主体内容长度
//空行 用来分割消息头和主体

    r.ParseForm()       //解析参数,默认是不会解析的
    fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
    fmt.Println("path", r.URL.Path)
    fmt.Println("scheme", r.URL.Scheme)
    fmt.Println(r.Form["url_long"])
    for k, v := range r.Form {
       fmt.Println("key:", k)
       fmt.Println("val:", strings.Join(v, ""))
    }
    fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端
}
func main() {
    http.HandleFunc("/", sayhelloName)
    err := http.ListenAndServe(":9090", nil)
    if err != nil {
       log.Fatal("ListenAndServe: ", err)
    }
}

    //用来处理接收客户端的请求信息
    defer l.Close()
    var tempDelay time.Duration // how long to sleep on accept failure
    for {
        rw, e := l.Accept()
        if e != nil {
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay  max {
                    tempDelay = max
                }
                log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c, err := srv.newConn(rw)
        //创建一个Conn
        if err != nil {
            continue
        }
        go c.serve()
        //单独开了一个goroutine
    }
}

如何分配handler?

如何具体分配到相应的函数来处理请求?

conn首先会解析request:c.readRequest,然后获取对应的handler:

handler :=c.server.Handler

Go web 基础相关知识

Go的http详解

Go的http有两个核心功能:Conn,ServeMux

Conn的goroutine

Go为了实现高并发和高性能,使用了goroutine来处理Conn的读写事件,这样每个请求都能保持独立

c, err := srv.newConn(rw)
if err != nil {
    continue
}
go c.serve()

客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中可以读取到相应的header信息,这样保证了每个请求的独立性

ServeMux的自定义(路由器的实现)

并发(Concurrency)是指在计算机科学中,指的是同时执行多个独立的任务或操作的能力。这些任务可能在同一时间段内交替执行,也可能在同一时间段内同时执行,但它们都是在同一个时间段内并发地进行。并发通常涉及到多个线程、进程或任务同时执行,并且这些执行之间可能相互独立,也可能存在一定的交互关系。并发通常被用来提高系统的性能、资源利用率和响应能力。

需要锁机制是为了防止只有一个进程能访问共享资源

type ServeMux struct {
    mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m  map[string]muxEntry  // 路由规则,一个string对应一个muxEntry实体,这里的string就是注册的路由表达式
    hosts bool // 是否在任意的规则中带有host信息
}
type muxEntry struct {
    explicit bool   // 是否精确匹配
    h        Handler // 这个路由表达式对应哪个handler
    pattern  string  //匹配字符串
}
//每个路由表达式都对应着一个handler
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}

我们调用了HandlerFunc(f),强制类型转换f称为HandlerFunc类型,这样f就拥有了ServeHTTP方法

HandlerFunc(f)-因为HandlerFunc实现了Handler接口类型

type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

默认路由器实现了ServeHTTP:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        w.Header().Set("Connection", "close")
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。

  • 首先调用Http.HandleFunc

    按顺序做了几件事:

    1 调用了DefaultServeMux的HandleFunc

    2 调用了DefaultServeMux的Handle

    3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则

  • 其次调用http.ListenAndServe(“:9090”, nil)

    按顺序做了几件事情:

    1 实例化Server

    2 调用Server的ListenAndServe()

    3 调用net.Listen(“tcp”, addr)监听端口

    4 启动一个for循环,在循环体中Accept请求

    5 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()

    6 读取每个请求的内容w, err := c.readRequest()

    7 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux

    8 调用handler的ServeHttp

    9 在这个例子中,下面就进入到DefaultServeMux.ServeHttp

    10 根据request选择handler,并且进入到这个handler的ServeHTTP

      mux.handler(r).ServeHTTP(w, r)
    

    11 选择handler:

    A 判断是否有路由能满足这个request(循环遍历ServeMux的muxEntry)

    B 如果有路由满足,调用这个路由handler的ServeHTTP

    C 如果没有路由满足,调用NotFoundHandler的ServeHTTP

    表单

    表单是一个包含表单元素的区域

    通过表单我们可以让客户端于服务器进行数据的交互

    表单元素是允许用户在表单中输入信息的元素

    ...
    input 元素
    ...
    
    

    Go web 基础相关知识

    4.1处理表单的输入

    package main
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"text/template"
    )
    func sayhelloName(w http.ResponseWriter, r *http.Request) {
    	r.ParseForm()
    	//解析url传递的参数,对于POST则解析相应包的主体
    	//如果没有调用ParseForm方法,下面无法获取表单的数据
    	fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
    	fmt.Println("path", r.URL.Path)
    	fmt.Println("scheme", r.URL.Scheme)
    	fmt.Println(r.Form["url_long"])
    	for k, v := range r.Form {
    		fmt.Println("key:", k)
    		fmt.Println("value:", v)
    	}
    	fmt.Fprintln(w, "Hello astaxie!")
    	//这个写入到w的是输出客户端的
    }
    func login(w http.ResponseWriter, r *http.Request) {
    	fmt.Println("method:", r.Method) //获取请求的方法
    	if r.Method == "GET" {
    		t, _ := template.ParseFiles("login.gtpl")
    		log.Println(t.Execute(w,nil))
    	}else{
    		//请求的要么是登录数据,要么是执行登录的逻辑判断
            //如查询数据库,验证登录信息等
    		fmt.Println("username:",r.Form["username"])
    		fmt.Println("password:",r.Form["password"])
    	}
    }
    func main() {
    	http.HandleFunc("/",sayhelloName) // 设置访问的路由
    	http.HandleFunc("/login",login)
    	err := http.ListenAndServe("9090",nil)
    	if err!=nil{
    		log.Fatal("ListenAndServe:",err);
    	}
    }
    

    4.2验证表单的输入

    如何在服务器端验证我们从一个表单元素中得到了一个值。

    //如果username是map的值必须通过这种形式
    if len(r.Form["username"][0])==0{
        //为空的处理
    }
    

    我们通过r.Form.Get()来获取单个值

    只能是数字

    如果想要确保一个表单输入框中获取的只能是数字

    getint,err:=strconv.Atoi(r.Form.Get("age"))
    if err!=nil{
        //数字转化出错了,那么可能就不是数字
    }
    //接下来就可以判断这个数字的大小范围了
    if getint >100 {
        //太大了
    }
    
    只能是中文

    于中文我们目前有两种方式来验证,可以使用 unicode 包提供的 func Is(rangeTab *RangeTable, r rune) bool 来验证,也可以使用正则方式来验证,这里使用最简单的正则方式,如下代码所示

    if m, _ := regexp.MatchString("^\p{Han}+$", r.Form.Get("realname")); !m {
        return false
    }
    
    英文
    if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("engname")); !m {
        return false
    }
    
    电子邮箱地址
    if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m {
        fmt.Println("no")
    }else{
        fmt.Println("yes")
    }
    
    手机号码
    if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m {
        return false
    }
    
    下拉菜单

    想要知道元素生成的下拉菜单中是否有被选中的项目

    假设我们的select是这样一些元素

    apple
    pear
    banana
    
    
    slice:=[]string{"apple","pear","banana"}
    v := r.Form.Get("fruit")
    for _, item := range slice {
        if item == v {
            return true
        }
    }
    return false
    
    单选按钮

    当我们需要判断一个单选按钮是否选中的时候

    男
    女
    

    和下拉菜单的做法一样

    slice:=[]string{"1","2"}
    for _, v := range slice {
        if v == r.Form.Get("gender") {
            return true
        }
    }
    return false
    
    复选框

    就是多选的

    足球
    篮球
    网球
    
    slice:=[]string{"football","basketball","tennis"}
    a:=Slice_diff(r.Form["interest"],slice)
    if a == nil{
        return true
    }
    return false
    
    func Slice_diff(slice1, slice2 []interface{}) (diffslice []interface{}) {
    	for _, v := range slice1 {
    		if !In_slice(v, slice2) {
    			diffslice = append(diffslice, v)
    		}
    	}
    	return
    }
    
    日期和时间

    你想确定日期时间是否有效

    t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
    fmt.Printf("Go launched at %s\n", t.Local())
    
    身份证号码

    身份证有十五位和十八位

    //验证15位身份证,15位的是全部数字
    if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m {
        return false
    }
    //验证18位身份证,18位前17位为数字,最后一位是校验位,可能为数字或字符X。
    if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m {
        return false
    }
    

    4.3预防跨站脚本

    我们的网站包含了大量的动态内容以提高用户体验,比过去要复杂的多,所谓动态内容,就是根据用户环境的需要,Web应用程序能够输出相应的内容 。

    对XSS(跨站脚本攻击)最佳的防护应该结合以下两种方法:

    1.对所有输入数据进行检验

    2.进行适当的处理,防止任何已经成功注入的脚本在浏览器端运行

    HTML转义

    HTML 转义是指将 HTML 特殊字符转换为对应的 HTML 实体,以防止恶意用户插入 HTML 或 JavaScript 代码导致的安全漏洞。HTML/template 包提供了几个函数来帮助进行 HTML 转义。

    在 web 应用程序中,如果允许用户输入的内容直接嵌入到 HTML 页面中而没有经过转义处理,就可能会导致安全漏洞。这种情况被称为跨站脚本攻击(Cross-Site Scripting,XSS)。

    XSS 攻击的原理是攻击者向网页中注入恶意的客户端脚本,当其他用户访问包含恶意脚本的页面时,这些脚本就会在用户浏览器中执行,从而达到攻击的目的,例如盗取用户的会话信息、篡改页面内容等。

    举个例子,如果一个网站允许用户在评论中输入任意内容,并且将这些内容直接显示在网页上,而不进行转义处理,那么攻击者可以在评论中插入恶意的 JavaScript 代码。当其他用户浏览这些评论时,恶意脚本就会在其浏览器中执行,可能导致用户的账户被盗取或者其他安全问题。

    因此,对用户输入的内容进行适当的转义处理是防范 XSS 攻击的重要手段之一。HTML 转义函数就是一种常见的转义处理方式,它将 HTML 特殊字符转换为对应的 HTML 实体,从而使得恶意脚本失效,保护了网站的安全。

    脚本

    脚本(Script)是一种用于自动化和控制计算机程序行为的编程语言。脚本通常以文本形式编写,并且可以被解释器直接执行,而不需要预先编译成二进制代码。脚本语言通常用于快速编写小型程序或自动化任务,如网页交互、系统管理、数据处理等。

    在 web 开发中,脚本通常指的是客户端脚本(Client-side Script),它是嵌入在网页中的脚本代码,由浏览器解释执行。常见的客户端脚本语言包括 JavaScript、VBScript 等。这些脚本可以在用户浏览网页时在浏览器中执行,用于实现网页的交互功能、动态效果等。

    举个例子,当你在网页上点击一个按钮,触发了一些动作或者弹出了一个对话框,很可能是因为网页中嵌入了 JavaScript 脚本,它在用户与网页交互时被触发执行。

    然而,如果恶意用户能够将自己的恶意脚本注入到网页中,并成功执行,就可能导致安全问题,比如跨站脚本攻击(XSS)或其他类型的安全漏洞。因此,开发者需要注意对用户输入数据进行适当的过滤和转义,以防止这类安全风险的发生。

    MD5(Message Digest Algorithm 5)是一种常用的哈希函数,用于将任意长度的消息(或数据)转换为固定长度的哈希值(通常为128位的二进制字符串,或者32个十六进制字符)。

    MD5

    MD5 哈希对象通常在计算机安全、数据完整性验证等领域中使用,其主要用途包括:

    数据完整性验证:** MD5 哈希函数可以将任意长度的数据转换为固定长度的哈希值。通过对原始数据进行哈希处理,可以生成一个唯一的标识符,称为哈希值。如果数据的任何部分发生了改变,其哈希值也会发生变化。因此,可以通过比较数据的哈希值来验证数据的完整性,确保数据在传输或存储过程中没有被篡改。

    package main
    import (
    	"crypto/md5"
    	"fmt"
    	"html/template"
    	"io"
    	"log"
    	"net/http"
    	"strconv"
    	"strings"
    	"time"
    )
    func sayhelloName(w http.ResponseWriter, r *http.Request) {
    	r.ParseForm() //解析url传递的参数,对于POST则解析响应包的主体(request body)
    	//注意:如果没有调用ParseForm方法,下面无法获取表单的数据
    	fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
    	fmt.Println("path", r.URL.Path)
    	fmt.Println("scheme", r.URL.Scheme)
    	fmt.Println(r.Form["url_long"])
    	for k, v := range r.Form {
    		fmt.Println("key:", k)
    		fmt.Println("val:", strings.Join(v, ""))
    	}
    	fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
    }
    func login(w http.ResponseWriter, r *http.Request) {
    	fmt.Println("method:", r.Method)
    	if r.Method == "GET" {
    		//如果时GET请求,就返回登录页面
    		crutime := time.Now().Unix()
    		//获取当前时间戳(当前时刻)
    		h := md5.New()
    		//创建一个MD5,MD5对象可以保证这个时间戳的安全性
    		io.WriteString(h, strconv.FormatInt(crutime, 10))
    		//将当前时间戳转换成字符串并写入哈希对象h
    		token := fmt.Sprintf("%x", h.Sum(nil))
    		//fmt.Sprintf用来返回格式化的字符串
    		//计算哈希值并将其格式化为十六进制字符串
    		t, _ := template.ParseFiles("vote.html")
    		//解析模板文件并赋值给t
    		t.Execute(w, token)
    		//将token(格式化的字符串)渲染到模板中
    		//发送给客户端
    	} else {
    		//如果时POST就需要处理 用户提交的登陆数据
    		r.ParseForm()
    		//解析表单数据
    		token := r.Form.Get("token")
    		if token != "" {
    			//验证token的合法性
    		} else {
    			//不存在token则报错
    		}
    		fmt.Println("username length:", len(r.Form["username"][0]))
    		fmt.Println("username:", template.HTMLEscapeString(r.Form.Get(("username"))))
    		//打印用户名,并适应HTML转义
    		fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
    		template.HTMLEscape(w, []byte(r.Form.Get("username"))) //将用户名转移之后,输出到客户端
    	}
    }
    func main() {
    	http.HandleFunc("/", sayhelloName)       //设置访问的路由
    	http.HandleFunc("/login", login)         //设置访问的路由
    	err := http.ListenAndServe(":9090", nil) //设置监听的端口
    	if err != nil {
    		log.Fatal("ListenAndServe: ", err)
    	}
    }
    
    4.5处理文件上传

    要使表单能够上传文件

    我们要添加form的enctype属性,enctype属性有三种情况:

    1. application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
    2. multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
    3. text/plain 空格转换为 "+" 加号,但不对特殊字符编码。
    package main
    import (
    	"crypto/md5"
    	"fmt"
    	"html/template"
    	"io"
    	"net/http"
    	"os"
    	"strconv"
    	"time"
    )
    func upload(w http.ResponseWriter, r *http.Request) {
    	fmt.Println("method", r.Method) //获取请求的方法
    	if r.Method == "GET" {
    		crutime := time.Now().Unix()
    		h := md5.New()
    		io.WriteString(h, strconv.FormatInt(crutime, 10))
    		token := fmt.Sprintf("%x", h.Sum(nil))
    		t, _ := template.ParseFiles("upload.html")
    		t.Execute(w, token)
    	} else {
    		r.ParseMultipartForm(32 
    			fmt.Println(err)
    			return
    		}
    		defer file.Close()
    		fmt.Fprintf(w, "%v", handler.Header)
    		f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
    		if err != nil {
    			fmt.Println(err)
    			return
    		}
    		defer f.Close()
    		io.Copy(f, file)
    	}
    }
    

免责声明
本网站所收集的部分公开资料来源于AI生成和互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
评论列表 (暂无评论,人围观)

还没有评论,来说两句吧...

目录[+]