又到了周末今天准备学习一下go web的入门,在这之前我觉得有必要总结一下上周自己遇到的一个问题。项目情况是这样的就是前端上传图片使用的是base64编码形式,自己使用了一个List<String>来接受上传的内容,然后后台解码成byte数组后生成图片,按理来说是一个很常规的操作了,但是自己却遇到了不少的问题。在springmvc中我使用了@RequestParam来接收对应的参数(事实证明这个确实是个败笔),因为当时自己调试接口使用的是idea自带的restful工具,自己将图片生成的base64编码内容传到方法里面确实成功了,而且各个流程都很顺利。但是和前台联调却一直走不通,前台改成数组,我也改数组(实际上string数组和List确实是可以相互转换的,这点没错),甚至我看网上有的出现传递数组时@RequestParam(value="xxx[]"),这样的形式,但是依然失败,因为前端参数名称根本就不是xxx[]这样的形式。后来自己同事建议我放到body里面接收,因为base64编码内容较长放在header里面确实很难接收,最后自己定义了一个包含List<String>的VO,并使用@RequestBody注解成功接收到参数,算是告一段落。其实我平时写代码比较喜欢将参数放到请求头里面,哪怕是多个参数也是一样,理论上确实没什么问题,但是如果遇到我这种上传文件,参数内容很长,我建议还是放到请求体。如果参数过多最好用VO封装一下(我之所以不喜欢用VO DTO这些是因为hybris中定义很麻烦,需要重新编译、重启,比较耗时间)。好了,接下来开始今天的go web入门。关于go的一些细节这里就不过多介绍,因为有的自己也不是很清楚,另外项目结构和java也不一样,一开始很难适应。
学习一门语言应该是从最开始的基础开始,但是之所以从web开始写项目是因为自己本来java也是做web方面的,自己要稍微熟悉一点,再一个也是因为自己学了一点vert.x的web基础,相互对比印证感觉也有利自己的理解吧。我本地go环境是1.11版本,开发者工具是GoLand,这个也是jet brains家族的产品,和idea比较相像。好了,开始正式的开发入门。
我先从最基本的"hello word"开始,go也需要一个main函数作为启动的入口,所以首先先建一个main函数,另外我需要有一个处理器处理用户请求,这里是helloHandler,另外还需要指定服务监听的端口号,代码如下:
func helloHandler(writer http.ResponseWriter, req *http.Request) {
fmt.Println("----> handler start <----")
//writer.Header().Set("content-type","application/json")
fmt.Fprintf(writer, "hello go, rquest url is %s",req.URL.Path)
}
func main() {
fmt.Println("----> go start <----")
http.HandleFunc("/hello", helloHandler)
err := http.ListenAndServe("localhost:9090", nil)
checkErr(err)
}
func checkErr(e error) {
if e != nil{
log.Fatal(e)
}
}
main函数里面首先是一个路由器,即将用户请求的URL和对应的处理器做一个映射,再一个就是监听的地址和端口,如果本地的话,也可以只保留端口号,"nil"参数即java中的null,这个参数是一个处理器,因为我们的请求路径和handler已经做了映射关系,所以我可以传入空值。因为http.ListenAndServe()方法会返回错误值,所以我对错误进行处理,只是简单的记录下来。如果觉得麻烦也可以直接使用log.Fatal(http.ListenAndServe())。log.Fatal()其实就是一个打印加上系统退出两个方法,也就是服务出现错误,那么会打印出错误信息,然后系统退出,服务停止。
helloHandler需要的参数一个是用户请求,这个是一个指针类型,还有一个就是响应的输出流,即输出服务返回的结果。fmt是一个格式话的包,里面都是和输出相关的比如println(),printf()等等。
接下来启动服务,并在浏览器访问:localhost:9090/hello,看到项目启动成功。
go有一个不一样的地方在于如果你的变量没有被使用会出错,所以有时候会出现"_"这种符号,表示返回的结果会被忽略(可能描述的不太准确,大概就是这个意思,因为go语言返回结果可能不止一个)。
这个只是输出内容到页面,那么我想进行页面跳转改怎么处理呢?go语言也有相应的包来处理,
现在我在项目下新建一个template包,下面存放我的html页面,并新建一个index.html。另外加一个跳转首页的handler。对代码进行修改如下:
func main() {
fmt.Println("----> go start <----")
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/index",indexHandler)
err := http.ListenAndServe("localhost:9090", nil)
checkErr(err)
}
func indexHandler(writer http.ResponseWriter, request *http.Request) {
fmt.Println("----> index handler start <----")
t,err := template.ParseFiles("template/index.html")
checkErr(err)
t.Execute(writer,nil)
}
这个方法里面是go自带的"html/template"包对本地的html页面转换成模板,template.ParseFiles()方法有两个返回结果,一个是模板对象(不知道这么说对不对,它的类型其实是一个指针,但是根据指针还是能拿到相应的对象),一个就是错误信息。最后执行模板对象的Execute()方法,两个参数一个是响应流,最后一个是动态数据,我们这里只做了最简单的跳转,所以传入空值。启动项目,然后浏览器访问:localhost:9090/index
当然实际开发肯定不能是这样的,更多的时候还是有一个总的控制器进行控制的。我们可以自己来实现一个控制器来实现将用户请求和对应的handler进行映射,就像springmvc的前端控制器一样,对用户的请求进行分发。go里面提供了一个默认的路由器ServeMux,具体就不介绍了,因为涉及到读写锁的问题我也还没搞懂!
下面是ServeMux ServeHTTP方法的源码:
我们可以使用它的ServeHTTP()方法,对用户请求进行分发。不过这里也可以参考ServeMux自己来模拟写一个类似的转发功能。在项目下新建一个webService包,并新建一个名称为CustomMux的type类型,并重写ServeHTTP()方法,代码如下:
type CustomMux struct {
}
func (mux *CustomMux) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
switch request.URL.Path {
case "/":
IndexHandler(writer,request)
return
case "/home":
HomePageHandler(writer,request)
return
case "/login":
LoginHandler(writer,request)
return
default:
http.NotFound(writer,request)
}
}
func LoginHandler(writer http.ResponseWriter, request *http.Request) {
log.Println("-----> login handler start <-----")
if request.Method == "Get" {
t,err := template.ParseFiles("template/login.html")
checkErr(err)
t.Execute(writer,request)
} else {
username := request.FormValue("username")
password := request.Form["password"]
fmt.Println("username: ", username)
fmt.Println("password: ", password)
http.Redirect(writer,request,"/home",302)
}
}
func HomePageHandler(writer http.ResponseWriter, request *http.Request) {
log.Println("-----> home page handler start <-----")
t,err := template.ParseFiles("template/homePage.html")
checkErr(err)
t.Execute(writer,"这是网站首页")
}
func IndexHandler(writer http.ResponseWriter, request *http.Request) {
log.Println("-----> index handler start <-----")
t,err := template.ParseFiles("template/login.html")
checkErr(err)
Name := "go template"
t.Execute(writer,Name)
}
func checkErr(e error) {
if e != nil {
log.Fatal(e)
}
}
当然我的CustomMux内部未定义任何的变量,ServeMux并不这样的,我只是做一个简单的模拟,ServeHTTP作为CustomMux对象的方法做的工作就是针对用户请求路径找到对应的处理器,并处理用户请求。"/"跳转登录页面,"/login"处理用户登录逻辑,成功后重定向到"/home","/home"跳转到主页,其中登录逻辑里面获取了用户请求参数,但是自己还没学数据库方面的内容,所以暂时略过。相关的登录页面和主页的html文件依然方法哦template文件夹下面。
如果使用我们自己定义的路由,那么需要改下main方法的内容,这里我写了两种方法,其实都是一样的,代码如下:
// 自定义全局变量
var mu webService.CustomMux
func main() {
fmt.Println("----> go start <----")
//http.HandleFunc("/hello", helloHandler)
//http.HandleFunc("/index",indexHandler)
mux := &webService.CustomMux{}
err := http.ListenAndServe("localhost:9090", mux)
//err := http.ListenAndServe("localhost:9090", &mu)
checkErr(err)
}
我们可以通过定义一个全局的CustomMux,然后将变量的地址作为handler传递给http.ListenAndServe(),也可以定义一个局部变量指向CustomMux的地址值,然后传递给http.ListenAndServe()。我们刚才传递第二个参数为nil,实际上使用的是默认的ServeMux来进行处理的。
接着重启服务,再次访问浏览器:localhost:9090/
没有问题,跳转到了登录页面,然后输入用户名和密码进行登录,同时将用户名和密码输出到控制台(数据库方面的知识后面再补充),然后看到页面跳转到主页了:
看浏览器的控制台也可以很直观的看到这个流程,请求的是localhost:9090/login
实际页面显示的首页,返回的状态码是302,也就是重定向,而重定向的location是"/home",没有问题。我们在看下控制台输出的用户名和密码值:
在loginHandler里面我做了一个判断,就是同过请求方法进行判断的,在login.html上我使用的是POST方法提交表单,而如果用户用GET请求,那么我只会继续显示登录页面信息,即不进行跳转。而如果是POST请求,取出用户提交参数,并进行校验做进一步的处理。取出用户数据的时候使用了两种方式,这两种方式都是可以的,看个人习惯。
在homePageHandler里面,t.Execute(writer,"这是网站主页")方法,向html页面写入了一段文字,然后在页面获取出来,但是go获取后台传递的数据使用的是"{{.}}"的形式,和java常用模板取值方式不太一样。还有就是根据对象属性获取"xx.yy"这种形式,等后面学习到了可以和数据库相关的内容做一个较为完整的web项目。
另外还有几种方式没有尝试,比如获取用请求URL上的参数,还有json格式的参数,文件上传,包括上传图片等等。这些留着以后有机会在尝试吧。
好了今天的学习就到这里了,go语言从自己目前的学习来看不是特别的难,只是有些思想和java不太一样,包括项目结构上,有时候感觉挺别扭的,不是很适应。当然像go比较厉害并发方面的内容,像协程啊、channel啊等等自己也是一知半解,希望随着不断的深入学习,自己也能慢慢理解吧。因为自己对go也都是刚刚入门,语法都还有很多要学的知识,所以写的内容难免会有不当之处,自己很多时候都是按照学习java的思路来学习的,也不知道对或者不对,如果有什么不当之处还请大家指正,谢谢大家!!
这次代码我上传到我的github,地址:https://github.com/ypcfly/learnGo,希望大家多多支持!!