时间:2023-06-02 01:39:01 | 来源:网站运营
时间:2023-06-02 01:39:01 来源:网站运营
从0开始Go语言-用Golang搭建网站(无依赖):Gopher:原译是囊地鼠,也就是Go语言Logo的那个小可爱;这里特指Go程序员给自己的昵称。
GOPATH环境变量指定工作区的位置。如果没有设置GOPATH,则假定在Unix系统上为GOPATH环境变量是用于设置Go编译可以执行文件、包源码以及依赖包所必要的工作目录路径,Go1.11后,新的木块管理虽然可以不再依赖$HOME/go
,在Windows上为%USERPROFILE%/go
。如果要将自定义位置用作工作空间,可以设置GOPATH环境变量。
$GOPATH/src
,但是依然需要使用 $GOPATH/pkg
路径来保存依赖包。GOPATH
:$ export GOPATH=$YOUR_PATH/go
$ source ~/.bash_profile
控制面板->系统->高级系统设置->高级->环境变量设置
go install
编译的可执行二进制文件go install
编译后的包文件,就会存放在这里go get
命令下载的源码包文件$ go env
GOARCH="amd64"GOBIN=""GOCACHE="/Users/zeta/Library/Caches/go-build"GOEXE=""GOFLAGS=""GOHOSTARCH="amd64"GOHOSTOS="darwin"GOOS="darwin"GOPATH="/Users/zeta/workspace/go"GOPROXY="https://goproxy.io"GORACE=""GOROOT="/usr/local/go"GOTMPDIR=""GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"GCCGO="gccgo"CC="clang"CXX="clang++"CGO_ENABLED="1"GOMOD=""CGO_CFLAGS="-g -O2"CGO_CPPFLAGS=""CGO_CXXFLAGS="-g -O2"CGO_FFLAGS="-g -O2"CGO_LDFLAGS="-g -O2"PKG_CONFIG="pkg-config"GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/7v/omg2000000000000019/T/go-build760324613=/tmp/go-build -gno-record-gcc-switches -fno-common"
$ mkdir gowebserver && cd gowebserver
$ touch main.go
package mainimport "fmt"func main() { fmt.Println("Hello, 世界")}
$ go run main.go
Hello, 世界
package
申明包 & import
导入包package
关键字。package
关键字必须是第一行出现的代码。main
import
关键字。默认情况下,导入包的包名与导入路径的最后一个元素一致,例如 import "math/rand"
,在代码中使用这个包时,直接使用rand
,例如 rand.New()
import "fmt"import "math/rand"
或者 分组import ( "fmt" "math/rand")
fmt包是Go语言内建的包,作用是输出打印。
func
关键字:定义函数func
是function的缩写, 在Go语言中是定义函数的关键字。func 函数名(参数1 类型,参数2 类型){ 函数体}
本例中定义了一个main函数。main
函数没有参数。main
函数体里调用fmt
包的Println
函数,在控制台输出字符串 “Hello, 世界”main.main()
,所以每一个可执行的Go程序都应该有一个main
包和一个main函数
。main.go
文件,修改代码如下:package mainimport ( "fmt" "net/http")func myWeb(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "这是一个开始")}func main() { http.HandleFunc("/", myWeb) fmt.Println("服务器即将开启,访问地址 http://localhost:8080") err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println("服务器开启错误: ", err) }}
保存文件,然后在命令行工具下输入命令,运行程序$ go run main.go
fmt.Println
打印出来的提示,在浏览器中访问 http://localhost:8080
你将访问到一个页面,显示 "这是一个开始"package main
,然后导入包。net/http
,这个包是官方的,实现http客户端和服务端的各种功能。Go语言开发Web服务的所有功能就是基于这个包(其他第三方的Go语言Web框架也都基于这个包,没有例外)main
函数里发生了什么http.HandleFunc("/", myWeb)
myWeb
。err := http.ListenAndServe(":8080", nil)
在这句,调用了http
包中的ListenAndServe
函数,该函数有两个参数,第一个是指定监听的端口号,第二个是指定处理请求的handler,通常这个参数填nil,表示使用默认的ServeMux作为handler。nil
就是其他语言里的null
。什么是handler?什么是ServeMux?
ServeMux就是一个HTTP请求多路由复用器。它将每个传入请求的URL与已注册模式的列表进行匹配,并调用与URL最匹配的模式的处理程序。
很熟悉吧?还记得前面的http.HandleFunc
吗?他就是给http包中默认的ServeMux(DefaultServeMux)添加URL与处理函数匹配。
通常都是使用http包中的默认ServeMux,所以在http.ListenAndServe
函数的第二个参数提供nil就可以了
ListenAndServe
函数会一直监听,除非强制退出或者出现错误。err
变量接收返回对象。紧接着,判断err
是否为空,打印出错误内容,程序结束。var
var str string = "my string"//^ ^ ^//关键字 变量名 类型
Go还提了一种简单的变量定义方式:=
,自动根据赋值的对象定义变量类型,用起来很像脚本语言:str := "my string"
if err != nil{ //处理....}
在Go语言中,这是很常见的错误处理操作,另一种panic异常,官方建议不要使用或尽量少用,暂不做介绍,先从err开始。if err := http.ListenAndServe(":8080", nil); err != nil { fmt.Println("服务器开启错误: ", err)}
err这个变量的生命周期只在if
块中有效。main
函数中,用http.HandleFunc
将 myWeb与路由/
匹配在一起。HandleFunc
函数定义了两个参数w
,r
,参数类型分别是http.ResponseWriter
和*http.Request
,w
是响应留写入器,r
是请求对象的指针。*
标记类型,说明这个参数需要的是这个类型的对象的指针。/
,请求对象和响应流写入器被传递给myWeb
函数,并由myWeb
函数负责处理这次请求。&
,取值用*
,例如:mystring := "hi"//取指针mypointer := &mystring//取值mystring2 := *mypointerfmt.Println(mystring,mypointer,mystring2)
把这些代码放在main
函数里,$ go run main.go
运行看看fmt.Fprintf(w, "这是一个开始")
再一次遇到老熟人fmt
,这次使用他的Fprintf
函数将字符串“这是一个开始”,写入到w
响应流写入器对象。w
响应流写入器里写入的内容最后会被Response输出到用户浏览器的页面上。/
路由main.go
文件,修改myWeb
函数,如下:func myWeb(w http.ResponseWriter, r *http.Request) { r.ParseForm() //它还将请求主体解析为表单,获得POST Form表单数据,必须先调用这个函数 for k, v := range r.URL.Query() { fmt.Println("key:", k, ", value:", v[0]) } for k, v := range r.PostForm { fmt.Println("key:", k, ", value:", v[0]) } fmt.Fprintln(w, "这是一个开始")}
运行程序$ go run main.go
curl --request POST / --url 'http://localhost:8080/?name=zeta' / --header 'cache-control: no-cache' / --header 'content-type: application/x-www-form-urlencoded' / --data description=hello
页面和终端命令行工具会答应出以下内容:key: name , value: zetakey: description , value: hello
http
请求的所有内容,都保存在http.Request
对象中,也就是myWeb
获得的参数 r
。r.ParseForm()
,作用是填充数据到 r.Form
和 r.PostForm
r.URL.Query()
函数返回的值 和 r.PostForm
值里的每一个参数。r.URL.Query()
和 r.PostForm
分别是URL参数对象和表单参数对象string
,值的类型是string
数组。在http协议中,无论URL和表单,相同名称的参数会组成数组。循环遍历:for...range
for
关键字,以下是Go中4种for
循环//无限循环,阻塞线程,用不停息,慎用!for{}//条件循环,如果a<b,循环,否则,退出循环for a < b{}//表达式循环,设i为0,i小于10时循环,每轮循环后i增加1for i:=0; i<10; i++{}//for...range 遍历objs,objs必须是map、slice、chan类型for k, v := range objs{}
前3种,循环你可以看作条件循环的变体(无限循环就是无条件的循环)。for...range
循环,遍历可遍历对象,并且每轮循环都会将键和值分别赋值给变量 k
和 v
html/template
"main
函数不变,增加导入html/template
包,然后修改myWeb
函数,如下:import ( "fmt" "net/http" "text/template" //导入模版包)func myWeb(w http.ResponseWriter, r *http.Request) { t := template.New("index") t.Parse("<div id='templateTextDiv'>Hi,{{.name}},{{.someStr}}</div>") data := map[string]string{ "name": "zeta", "someStr": "这是一个开始", } t.Execute(w, data) // fmt.Fprintln(w, "这是一个开始")}
在命令行中运行 $ go run main.go
,访问 http://localhost:8080
<div id='templateTextDiv'>Hi,{{.name}},{{.someStr}}</div>
中的{{.name}}
和{{.someStr}}
被替换成了 zeta
和这是一个开始
。并且,不再使用fmt.Fprintln
函数输出数据到Response了index.html
,并写入一些HTML代码 (我不是个好前端)<html><head></head><body> <div>Hello {{.name}}</div> <div>{{.someStr}}</div></body></html>
myWeb
函数func myWeb(w http.ResponseWriter, r *http.Request) { //t := template.New("index") //t.Parse("<div>Hi,{{.name}},{{.someStr}}<div>") //将上两句注释掉,用下面一句 t, _ := template.ParseFiles("./templates/index.html") data := map[string]string{ "name": "zeta", "someStr": "这是一个开始", } t.Execute(w, data) // fmt.Fprintln(w, "这是一个开始")}
在运行一下看看,页面按照HTML文件的内容输出了,并且{{.name}}和{{.someStr}}也替换了,对吧?template
包的核心功能就是将HTML字符串解析暂存起来,然后调用Execute
的时候,用数据替换掉HTML字符串中的{{}}
里面的内容t:=template.New("index")
初始化一个template对象变量,然后用调用t.Parse
函数解析字符串模版。t.Execute
函数,不仅用数据渲染模版,还替代了fmt.Fprintln
函数的工作,将输出到Response数据流写入器中。template
包的ParseFiles
函数,直接解析相对路径下的index.html文件并创建对象变量。map
类型 和 赋值给“_
”map
的初始化可以使用make
:var data = make(map[string]string)data = map[string]string{}
make是内置函数,只能用来初始化 map、slice 和 chan,并且make函数和另一个内置函数new不同点在于,它返回的并不是指针,而只是一个类型。map赋值于其他语言的字典对象相同,取值有两种方式,请看下面的代码:
data["name"]="zeta" //赋值name := data["name"] //方式1.普通取值name,ok := data["name"] //方式2.如果不存在name键,ok为false
代码中的变量ok,可以用来判断这一项是否设置过,取值时如果项不存在,是不会异常的,取出来的值为该类型的零值,比如 int类型的值,不存在的项就为0;string类型的值不存在就为空字符串,所以通过值是否为0值是不能判断该项是否设置过的。Go中的map还有几个特点需要了解:
ok,会获得true 或者 false,判断该项是否设置过,true为存在,false为不存在于map中。
map
的项的顺序是不固定的,每次遍历排列的顺序都是不同的,所以不能用顺序判断内容map
可以用for...range
遍历map
在函数参数中是引用传递(Go语言中,只有map、slice、chan是引用传递,其他都是值传递)_
" 就是用来解决这个问题的,_
用来丢弃函数的返回值。比如本例中,template.ParseFiles("./templates/index.html")
除了返回模版对象外,还会返回一个error
对象,但是这样简单的例子,出错的可能性极小,所以我不想处理error
了,将error
返回值用“_
”丢弃掉。注意注意注意:在实际项目中,请不要丢弃error,任何意外都是可能出现的,丢弃error会导致当出现罕见的意外情况时,非常难于Debug。所有的error都应该要处理,至少写入到日志或打印到控制台。(切记,不要丢弃 error ,很多Gopher们在这个问题上有大把的血泪史)OK,到目前为止,用Go语言搭建一个简单的网页的核心部分就完成了。
func main() { http.HandleFunc("/", myWeb) //指定相对路径./static 为文件服务路径 staticHandle := http.FileServer(http.Dir("./static")) //将/js/路径下的请求匹配到 ./static/js/下 http.Handle("/js/", staticHandle) fmt.Println("服务器即将开启,访问地址 http://localhost:8080") err := http.ListenAndServe(":8080", nil) if err != nil { fmt.Println("服务器开启错误: ", err) }}
在项目的根目录下创建static目录,进入static目录,创建js目录,然后在js目录里创建一个index.js文件。alert("Javascript running...");
打开之前的index.html文件,在</body>后面加上 <script src="/js/index.js"></script>
$ go run main.go
,访问 http://localhost:8080,页面会弹出提示框。<script src="/js/index.js"></script>
浏览器会请求 /js/index.js
这个路径/js/
,于是用文件服务处理这次请求,匹配到程序运行的路径下相对路径./static/js
。main.go
文件中这两句//指定相对路径./static 为文件服务路径 staticHandle := http.FileServer(http.Dir("./static")) //将/js/路径下的请求匹配到 ./static/js/下 http.Handle("/js/", staticHandle)
也可以写成一句,更容易理解//浏览器访问/js/ 将会以静态文件形式访问目录 ./static/jshttp.Handle("/js/", http.FileServer(http.Dir("./static")))
很简单...但是,可能还是不满足需求,因为, 如果http.Handle("/js/", http.FileServer(http.Dir("./static")))
对应到 ./static/jshttp.Handle("/css/", http.FileServer(http.Dir("./static")))
对应到 ./static/csshttp.Handle("/img/", http.FileServer(http.Dir("./static")))
对应到 ./static/imghttp.Handle("/upload/", http.FileServer(http.Dir("./static")))
对应到 ./static/uploadhttp.StripPrefix
剥开前缀,如下://http.Handle("/js/", http.FileServer(http.Dir("./static"))) //加上http.StripPrefix 改为 : http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./static"))))
这样,浏览器中访问/js/时,直接对应到./static目录下,不需要再加一个/js/子目录。http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("./js"))))
对应到 ./jshttp.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("./css"))))
对应到 ./csshttp.Handle("/img/", http.StripPrefix("/img/", http.FileServer(http.Dir("./img"))))
对应到 ./imghttp.Handle("/upload/", http.StripPrefix("/upload/", http.FileServer(http.Dir("./upload"))))
对应到 ./uploadgo run
命令运行程序。go run
都会重新编译源码,如何将程序运行在没有Go环境的计算机上?go build
命令,它会编译源码,生成可执行的二进制文件。go build
命令什么参数都不用加,它会自动查找目录下的main包下的main()函数,然后依次查找依赖包编译成一个可执行文件。go build
会编译为和开发操作系统对应的可执行文件,如果要编译其他操作系统的可执行文件,需要用到交叉编译。GOOS=windows GOARCH=amd64 go build
SET GOOS=linuxSET GOARCH=amd64go build main.go
关键词:依赖,语言