使用Golang 搭建http web服务器

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

Golang在搭建web服务器方面的能力是毋庸置疑的。官方已经有提供net/http包为搭建http服务器做准备。使用这个包能很简单地对web的路由,静态文件,模版,cookie等数据进行设置。至于这个包是否好用,这个就见仁见智了。你可以从net包开始封装一个web框架,当然也可以基于http包封装一个web框架。但是不论你是打算怎么样做,了解基本的net/http包一定是你借鉴的基础。

需求

我们要做两个简单的后台web系统。这个系统简单到只有两个页面:登陆和首页。

1 登陆页面

clip_image001[4]

 

登陆页面需要提交用户名和密码,将用户名和密码和mysql数据库中的用户名密码比对达到验证的效果。mysql数据库的go语言驱动推荐使用mymysql(https://github.com/ziutek/mymysql)。

 

当用户名和密码正确的时候,需要在cookie中种下用户名和加密后的密钥来进行cookie认证。我们不对cookie设置ExpireTime,这样这个cookie的有效期就是浏览器打开到浏览器关闭的session期间。

 

另外,这个页面还需要加载一个js。提交用户名和密码的是由js进行ajax post命令进行查询的。

 

这个页面需要加载css,进行页面排版

 

2 首页

clip_image002[4]

首页是非常简单,但它是一个动态页面。

 

首先右上角的”欢迎登陆, 管理员:yejianfeng“。这里的用户名yejianfeng是根据不同的用户会进行变化的。这里需要用到模版,我们又会用到了一个模版包html/template。这个包的作用就是加载模版。

 

其次这个页面也需要的是css,js(退出系统的删除cookie操作由js控制)

路由

分析下访问路径,会有几个文件:

/admin/index -- 首页

/login/index --登陆页显示

/ajax/login -- 登陆的ajax动作

/css/main.css -- 获取main的css文件

/js/login.js -- 获取登陆的js文件

 

在net/http包中,动态文件的路由和静态文件的路由是分开的,动态文件使用http.HandleFunc进行设置静态文件就需要使用到http.FileServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
 
import (
     "net/http"
)
 
func main() {
     http.Handle( "/css/" , http.FileServer(http.Dir( "template" )))
     http.Handle( "/js/" , http.FileServer(http.Dir( "template" )))
     
     http.HandleFunc( "/admin/" , adminHandler)
     http.HandleFunc( "/login/" ,loginHandler)
     http.HandleFunc( "/ajax/" ,ajaxHandler)
     http.HandleFunc( "/" ,NotFoundHandler)
     http.ListenAndServe( ":8888" , nil)
 
}

这里的http.FileServer(http.Dir("template"))的路径是怎么算的要注意下了。相对路径都是从当前执行路径路径下开始算的,这里设置的路径树是这样:

clip_image003[4]

关于http.HandleFunc如果不理解请参考我的上一篇文章

http://www.cnblogs.com/yjf512/archive/2012/08/22/2650873.html

 

处理器

这里需要定制4个handler对应相应的一级路径。我们将这些个handler都放入到route.go文件中

页面404处理器

main中的逻辑是当/admin/ /login/ /ajax/都不符合路径的时候就进入404页面处理器NotFoundHandler

1
2
3
4
5
6
7
8
9
10
11
12
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
     if  r.URL.Path ==  "/"  {
         http.Redirect(w, r,  "/login/index" , http.StatusFound)
     }
     
     t, err := template.ParseFiles( "template/html/404.html" )
     if  (err != nil) {
         log.Println(err)
     }
     t.Execute(w, nil)
 
}

 

这段逻辑是很清晰的:如果路径是"/" 即访问路径是http://192.168.100.166:8888/ 的时候,访问会跳转到登陆页面,否则当访问路径不满足制定的路由,读取404模版,显示404。

 

强烈推荐使用template.ParseFile模版文件解析

template包中也提供了Parse这类直接在代码中写上模版的函数。使用起来,你会发现这类函数远没有ParseFile好用,原因很明显,如果你把模版写在代码中,一旦模版需要进行小细节的修改,也需要重新编译。并且我们使用模版的目的就是将显示逻辑和业务逻辑分离,Parse会导致整个代码是不可维护!

当然有人会考虑到效率问题,一个是读取文件,一个是直接读取内存。但是我觉得这点效率差别用户根本不会察觉到,牺牲极小的性能保证工程性是很值得的。

 

ParseFile中的路径问题也是很容易犯的问题。这里的填写相对路径,则文件是从当前执行的路径开始算。

clip_image004[4]

比如这个路径,执行bin/webdemo,template/html/404.html就对应/go/gopath/template/html/404.html

 

这一步后续动作是在template/html文件夹中创建404.html页面

登陆页面处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func loginHandler(w http.ResponseWriter, r *http.Request) {
     pathInfo := strings.Trim(r.URL.Path,  "/" )
     parts := strings.Split(pathInfo,  "/" )
     var  action =  ""
     if  len(parts) > 1 {
         action = strings.Title(parts[1]) +  "Action"
     }
 
     login := &loginController{}
     controller := reflect.ValueOf(login)
     method := controller.MethodByName(action)
     if  !method.IsValid() {
         method = controller.MethodByName(strings.Title( "index" ) +  "Action" )
     }
     requestValue := reflect.ValueOf(r)
     responseValue := reflect.ValueOf(w)
     method.Call([]reflect.Value{responseValue, requestValue})
}

 

 

根据MVC思想,对具体的逻辑内容使用不同的Controller,这里定义了一个loginController, 使用reflect将pathInfo映射成为controller中的方法。

这样做的好处显而易见:

1 清晰的文件逻辑。不同的一级目录分配到不同的控制器,不同的控制器中有不同的方法处理。这些控制器放在一个独立的文件中,目录结构清晰干净。

2 路由和业务逻辑分开。 main中的http.HandleFunc处理一级路由,route处理二级路由,controller处理业务逻辑,每个单元分开处理。

 

好了,下面需要定义loginContrller,我们另外起一个文件loginController.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
 
import (
     "net/http"
     "html/template"
)
 
type loginController  struct  {
}
 
func ( this  *loginController)IndexAction(w http.ResponseWriter, r *http.Request) {
     t, err := template.ParseFiles( "template/html/login/index.html" )
     if  (err != nil) {
         log.Println(err)
     }
     t.Execute(w, nil)
}

 

 

下面需要创建template/html/login/index.html

这个文件中包含mian.css, jquery.js, base.js。 创建这些css和js。具体的css和js内容请看github源码

js中的逻辑是这样写的:当login表单提交的时候,会调用/ajax/login进行验证操作,下面就开始写ajax的处理器

ajax处理器

route中的ajaxHandler和loginHandler是大同小异,这里就忽略不说了,具体说一下ajaxController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main
 
import (
     "net/http"
     "github.com/ziutek/mymysql/autorc"
     "github.com/ziutek/mymysql/thrsafe"
     "encoding/json"
)
 
type Result  struct {
     Ret  int
     Reason  string
     Data  interface {}
}
 
type ajaxController  struct  {
}
 
func ( this  *ajacController)LoginAction(w http.ResponseWriter, r *http.Request) {
     w.Header().Set( "content-type" "application/json" )
     err := r.ParseForm()
     if  err != nil {
         OutputJson(w, 0,  "参数错误" , nil)
         return
     }
     
     admin_name := r.FormValue( "admin_name" )
     admin_password := r.FormValue( "admin_password" )
     
     if  admin_name ==  ""  || admin_password ==  "" {
         OutputJson(w, 0,  "参数错误" , nil)
         return
     }
     
     db := mysql.New( "tcp" "" "192.168.199.128" "root" "test" "webdemo" )
     if  err := db.Connect(); err != nil {
         OutputJson(w, 0,  "数据库操作失败" , nil)
         return
     }
     defer db.Close()
     
     rows, res, err := db.Query( "select * from webdemo_admin where admin_name = '%s'" , admin_name)
     if  err != nil {
         OutputJson(w, 0,  "数据库操作失败" , nil)
         return
     }
     
     name := res.Map( "admin_password" )
     admin_password_db := rows[0].Str(name)
     
     if  admin_password_db != admin_password {
         OutputJson(w, 0,  "密码输入错误" , nil)
         return
     }
     
     // 存入cookie,使用cookie存储
     expiration := time.Unix(1, 0)
     cookie := http.Cookie{Name:  "admin_name" , Value: rows[0].Str(res.Map( "admin_name" )), Path:  "/" }
     http.SetCookie(w, &cookie)
     
     OutputJson(w, 1,  "操作成功" , nil)
     return
}
 
func OutputJson(w http.ResponseWriter, ret  int , reason  string , i  interface {}) {
     out  := &Result{ret, reason, i}
     b, err := json.Marshal( out )
     if  err != nil {
         return
     }
     w.Write(b)
}

 

这段代码有几个地方可以看看:

如何设置header:

w.Header().Set("content-type", "application/json")

 

如何解析参数:

err := r.ParseForm()

admin_name := r.FormValue("admin_name")

 

如何连接数据库

db := mysql.New("tcp", "", "192.168.199.128", "root", "test", "webdemo")

 

当然这里得需要有个数据库和数据表,建表和建表的sql文件在github上能看到

create table webdemo_admin

(

admin_id int not null auto_increment,

admin_name varchar(32) not null,

admin_password varchar(32) not null,

primary key(admin_id)

);

 

如何设置cookie

cookie := http.Cookie{Name: "admin_name", Value: rows[0].Str(res.Map("admin_name")), Path: "/"}

http.SetCookie(w, &cookie)

 

主页处理器

adminHandler的逻辑比其他Handler多的一个是需要 获取cookie

cookie, err := r.Cookie("admin_name")

 

并且在传递给controller的时候需要将admin_name传递进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func adminHandler(w http.ResponseWriter, r *http.Request) {
     // 获取cookie
     cookie, err := r.Cookie( "admin_name" )
     if  err != nil || cookie.Value ==  "" {
         http.Redirect(w, r,  "/login/index" , http.StatusFound)
     }
     
     pathInfo := strings.Trim(r.URL.Path,  "/" )
     parts := strings.Split(pathInfo,  "/" )
     
     admin := &adminController{}
     controller := reflect.ValueOf(admin)
     method := controller.MethodByName(action)
     if  !method.IsValid() {
         method = controller.MethodByName(strings.Title( "index" ) +  "Action" )
     }
     requestValue := reflect.ValueOf(r)
     responseValue := reflect.ValueOf(w)
     userValue := reflect.ValueOf(cookie.Value)
     method.Call([]reflect.Value{responseValue, requestValue, Uservalue})
}

其他的部分都是一样的了。

它对应的Controller的Action是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type User  struct  {
     UserName  string
}
 
type adminController  struct  {
}
 
func ( this  *adminController)IndexAction(w http.ResponseWriter, r *http.Request, user  string ) {
     t, err := template.ParseFiles( "template/html/admin/index.html" )
     if  (err != nil) {
         log.Println(err)
     }
     t.Execute(w, &User{user})
}

这里就将user传递出去给admin/index模版

模版内部使用{{.UserName}}进行参数显示

 

后记

至此,这个基本的webdemo就完成了。启动服务之后,就会在8888端口开启了web服务。

当然,这个web服务还有许多东西可以优化,个人信息验证,公共模板的提炼使用,数据库的密码使用密文等。但是这个例子中已经用到了搭建web服务器最基本的几个技能了。

 

源代码

本文所涉及的源代码放在github上

https://github.com/jianfengye/webdemo

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
Go
Go 使用标准库 net/http 包构建服务器
Go 使用标准库 net/http 包构建服务器
48 0
|
4月前
|
缓存 监控 中间件
构建高效的Go语言Web服务器:基于Fiber框架的性能优化实践
在追求极致性能的Web开发领域,Go语言(Golang)凭借其高效的并发处理能力、垃圾回收机制及简洁的语法赢得了广泛的青睐。本文不同于传统的性能优化教程,将深入剖析如何在Go语言环境下,利用Fiber这一高性能Web框架,通过精细化配置、并发策略调整及代码层面的微优化,构建出既快速又稳定的Web服务器。通过实际案例与性能测试数据对比,揭示一系列非直觉但极为有效的优化技巧,助力开发者在快节奏的互联网环境中抢占先机。
|
7月前
|
JSON 自然语言处理 网络协议
【字节跳动青训营】后端笔记整理-2 | Go实践记录:猜谜游戏,在线词典,Socks5代理服务器
猜数字游戏也算是入门一门编程语言必写的程序了。通过这个程序,我们可以熟悉Go语言中的输入输出、流程控制与随机函数的调用。
98 2
|
7月前
|
中间件 Go API
Golang深入浅出之-Go语言标准库net/http:构建Web服务器
【4月更文挑战第25天】Go语言的`net/http`包是构建高性能Web服务器的核心,提供创建服务器和发起请求的功能。本文讨论了使用中的常见问题和解决方案,包括:使用第三方路由库改进路由设计、引入中间件处理通用逻辑、设置合适的超时和连接管理以防止资源泄露。通过基础服务器和中间件的代码示例,展示了如何有效运用`net/http`包。掌握这些最佳实践,有助于开发出高效、易维护的Web服务。
90 1
|
7月前
|
负载均衡 Go 调度
使用Go语言构建高性能的Web服务器:协程与Channel的深度解析
在追求高性能Web服务的今天,Go语言以其强大的并发性能和简洁的语法赢得了开发者的青睐。本文将深入探讨Go语言在构建高性能Web服务器方面的应用,特别是协程(goroutine)和通道(channel)这两个核心概念。我们将通过示例代码,展示如何利用协程处理并发请求,并通过通道实现协程间的通信和同步,从而构建出高效、稳定的Web服务器。
|
开发框架 前端开发 Linux
Go语言实战框架,GoFly全栈开发社区的Go快速开发框架简介与阿里服务器部署说明
GoFly中后台框架永久开源可商用。api文档管理并一键生成api接口代码,一键生成 CRUD前后端代码, GoFly快速开发框架是一款基于Go语言的 Gin和 Vue3的Arco Design的快速后台开发框架,基于JWT接口验证和Auth验证的权限管理系统,附件管理系统,天生支持saas架构。可打包部署在阿里云Linux系统上。
550 1
|
7月前
|
中间件 Go
【Go语言专栏】使用Go语言编写HTTP服务器
【4月更文挑战第30天】本文介绍了如何使用Go语言创建基本的HTTP服务器,包括设置路由、处理请求和响应。首先确保安装了Go环境,然后引入`net/http`包,定义路由和处理器函数。处理器函数接收`http.ResponseWriter`和`*http.Request`参数,用于发送响应和处理请求。使用`http.ListenAndServe`启动服务器,并可通过中间件增强功能。文章还提及了处理复杂请求、查询参数和POST数据的方法,以及使用第三方库如Gin和Echo扩展功能。通过本文,读者可掌握Go语言编写HTTP服务器的基础知识。
71 0
|
7月前
|
存储 缓存 网络协议
Go语言并发编程实战:构建高性能Web服务器
【2月更文挑战第6天】本文将通过构建一个高性能的Web服务器实战案例,深入探讨如何在Go语言中运用并发编程技术。我们将利用goroutine和channel实现高效的请求处理、资源管理和并发控制,以提升Web服务器的性能和稳定性。通过这一实战,你将更好地理解和掌握Go语言在并发编程方面的优势和应用。
|
Go
Go http包建立Web服务器
Go http包建立Web服务器
87 0
|
7月前
|
JSON 缓存 中间件
Go语言网络编程:深入探索HTTP服务器开发
【2月更文挑战第12天】本文将详细探讨使用Go语言开发HTTP服务器的过程,包括HTTP协议的理解、Go标准库中`net/http`包的使用、路由处理、中间件、静态文件服务、JSON处理以及性能优化等方面。通过本文,读者将能够掌握构建高效、可扩展HTTP服务器的关键技术。