GitHub:

项目文档: https://github.com/fentender/book-blog-doc

客户端: https://github.com/fentender/book-blog-client

服务端: https://github.com/fentender/book-blog-server

目录

前言

一、API设计

二、Swagger Editor使用

1.编写API文档

2.自动化生成项目需求文档

API Doc文档

客户端

服务端

三、BookBlog客户端

1.使用vue-cli建立项目

2. book_blog_api包的使用

3.使用mock拦截请求,模拟数据并返回

4.客户端JWT认证

5.客户端结构

四、BookBlog服务端

1.API的编写

2.Postman的使用

3.JWT的服务端实现

五、前后端耦合

六、项目展示


前言

此次项目以API-First为原则,利用Web客户端调用远端服务以开发一个前后端分离的Web博客。简单回顾此次项目开发历程,可以将其大致分为以下几步:

  1. 设计本次项目中博客所需要使用到RESTful风格的API
  2. 使用Swagger Editor编写文档,并生成利用其自动化生成客户端与服务端的API接口代码
  3. 利用mock模拟返回数据以进行测试,独立开发客户端项目
  4. 利用postman模拟请求,独立开发服务端项目
  5. 前后端开发完成后,进行测试,完善前后端项目

同时在本次的项目博客详细情况如下:

  1. 包含资源类型:Book,User,Bookshelf,Review,Token(书局来源于豆瓣读书)
  2. 通过/books, /reviews/{bookID},/users/{username}/bookshelfs等可获得简单 API 服务列表,并且支持分页
  3. Bookshelf资源与User资源支持token认证,只能在登录后才能使用。

一、API设计

在本次项目中使用了JWT的Token身份认证,设计思路如下:

  1. 客户端通过用户名和密码向服务器发送请求登陆
  2. 服务器收到请求数据,在数据库进行查询验证
  3. 如果验证成功,服务器签发一个Token给客户端
  4. 客户端可以将Token存放到SessionStroage 或者Cookie里
  5. 客服端设置监听,每次跳转路由,就判断 SessionStroage 中有无 Token ,没有就跳转到登录页面,有则跳转到对应路由页面
  6. 在每次客户端发送的请求中,在请求头中加上Token
  7. 在后端设置拦截器,用户登录后的每次请求都会经过这个拦截器校验Token是否有效
  8. 如果验证成功,则继续执行请求,返回请求到的数据

因此除了普通资源的API设计外,还需要增加用于登录、注册和注销的几个API。在本次项目BookBlog中有如下4种资源:User、Book、Bookshelf、Review、Token。其API服务如下:

{//Book"getBook" : GET "/books/{bookId}","getBooks" : GET "/books",//Review"getReview" : GET "/review/{reviewId}","getReviews" : GET "/reviews",//User"getUser" : GET "/users/user",//Bookshelf"createBookshelf" : POST "/user/{username}/bookshelfs","getBookshelfs" : GET "/users/{username}/bookshelfs","getBookshelf" : GET "/users/{username}/bookshelfs/{bookshelfName}","deleteBookshelf" : DELETE "/users/{username}/bookshelfs/{bookshelfName}",//添加书籍到书架中"addBookInBookshelf" : POST "/users/{username}/bookshelfs/{bookshelfName}/{bookId}","deleteBookInBookshelf" : DELETE "/users/{username}/bookshelfs/{bookshelfName}/{bookId}",//token相关资源,用于账户JWT验证"signIn" : GET "/token","signOut" : DELETE "/token","signUp" : POST "/token"
}

二、Swagger Editor使用

OpenAPI 规范(OAS)是一种通用的、和编程语言无关的 API 描述规范,使人类和计算机都可以发现和理解服务的功能,而无需访问源代码、文档或针对接口进行嗅探。而Swagger 规范即是OpenAPI的原身。Swagger则可以说是一个 OpenAPI 的工具集。Swagger Editor是Swagger中的一个编写OpenAPI的编辑器。其使用yaml语法来编写API文档,并且可以实时响应,使用漂亮的UI来测试效果

Swagger官网: https://swagger.io/

Swagger Editor在线编辑:https://editor.swagger.io/

1.编写API文档

Swagger Editor使用yaml语法进行编写API文档,采用OpenAPI规范描述API,并能够根据代码实时响应生成UI效果,让我们直观地看到效果。

可以通过查阅OpenAPI文档与查看官方例子学习其使用用法。

(需要注意的是XMLHttpRequest不支持GET请求中带body参数,而swagger在生成javascript时使用的superagent包是通过XMLHttpRequest发送请求的,我目前还没找到解决方案。因此在编写API文档时,注意尽量不要在GET请求中带body参数)

yaml教程:http://www.ruanyifeng.com/blog/2016/07/yaml.html

OpenAPI文档:https://swagger.io/docs/specification/api-host-and-base-path/

以下即为本次项目BookBlog使用Swagger Editor生成的文档展示

2.自动化生成项目需求文档

Swagger Editor能够根据所编写的文档自动生成客户端、服务端接口或是API文档。

选择Swagger Editor菜单栏:

API Doc文档

"Generate Client" - "html2":

导出html格式的API Doc文档。

客户端

"Generate Client" - "javascript" :

导出JavaScript编写的客户端API接口代码。其文件结构如下:

javascript-client
|-- git_push.sh
|-- mocha.opts
|-- package.json
|-- README.md
|-- docs
|   |-- Book.md
|   |-- BookApi.md
|   ...
|   |-- UserApi.md
|   `-- UserBookshelfApi.md
|-- src
|   |-- ApiClient.js
|   |-- api
|   |   |-- BookApi.js
|   |   ...
|   |   |-- UserApi.js
|   |   `-- UserBookshelfApi.js
|   |-- index.js
|   `-- model
|       |-- Book.js
|       ...
|       |-- Token.js
|       `-- User.js
`-- test|-- api|   |-- BookApi.spec.js|   ...|   |-- UserApi.spec.js|   `-- UserBookshelfApi.spec.js|-- assert-equals.js`-- model|-- Book.spec.js...|-- Token.spec.js`-- User.spec.js

可以看到由Swagger editor生成的客户端接口文档实际上就是一个npm包。其中各个文件作用如下:

  • docs: 各个API的描述
  • src: 存放使用javascript编写的API接口方法,其使用superagent(一个轻量的Ajax API)向服务端发送请求。
  • test:文件夹下为API接口方法的测试文档,可在根目录下使用npm run test命令进行mocha测试。
  • mocha.opts: mocha测试的配置文档
  • git_push.sh: 用于快速推送文档至git上的sh脚本
  • package.json: bookblogapi客户端的npm模块描述文件。
  • README.md: 客户端API接口的使用方法

为了在之后的前端项目中使用这个生成的npm包,将其公布在github: https://github.com/fentender/book_blog_api。在之后的客户端项目中就可以使用以下命令安装api接口npm包。

npm install fentender/book_blog_api --save 

服务端

"Generate Server" - "go-server" :

导出go编写的服务端代码,其结构如下:

go-server-server|-- api|   `-- swagger.yaml|-- go|   |-- README.md|   |-- api_book.go|   |-- api_review.go|   |-- api_user.go|   |-- api_user_bookshelf.go|   |-- logger.go|   |-- model_book.go|   |-- model_books.go|   |-- model_bookshelf.go|   |-- model_bookshelfs.go|   |-- model_review.go|   |-- model_reviews.go|   |-- model_user.go|   `-- routers.go`-- main.go

各个文件作用如下:

go/module_xxx.go: 定义了API中各种数据类型的结构。

//model_book.go
type Book struct {BookId int32 `json:"bookId"`BookName string `json:"bookName,omitempty"`Autor string `json:"autor,omitempty"`Info string `json:"info,omitempty"`
}

go/logger.go: 为每个路由的处理函数都注册了一个log.Printf()语句,即在服务端运行时,会打印出接收到的每一个有效请求的信息

//logger.go
func Logger(inner http.Handler, name string) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {start := time.Now()inner.ServeHTTP(w, r)log.Printf("%s %s %s %s",r.Method,r.RequestURI,name,time.Since(start),)})
}

go/router.go: 为我们实现了路由匹配和匹配函数调用

//router.go
type Route struct {Name        stringMethod      stringPattern     stringHandlerFunc http.HandlerFunc
}type Routes []Routefunc NewRouter() *mux.Router {router := mux.NewRouter().StrictSlash(true)for _, route := range routes {var handler http.Handlerhandler = route.HandlerFunchandler = Logger(handler, route.Name)        //调用logger.go进行注册,使得每个有效的路由请求信息被打印出来router.Methods(route.Method).Path(route.Pattern).Name(route.Name).Handler(handler)}return router
}func Index(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Hello World!")
}var routes = Routes{Route{"Index","GET","/",Index,},...
}

三、BookBlog客户端

本次项目使用vue.js框架,并利用vue-cli以开发单页面应用的博客。在开发过程中使用vue router、vuex进行一些功能上的辅助,并在开发过程中使用mock.js进行mock 测试。最后使用Ajax-hook包拦截客户端向服务端发送的请求添加token以完成jwt验证功能。

vue官网: https://cn.vuejs.org/

vue-cli: https://cli.vuejs.org/zh/

mock.js: http://mockjs.com/

Ajax-hook: https://github.com/wendux/Ajax-hook

1.使用vue-cli建立项目

在vue-cli安装完成后,使用以下命令建立bookblog项目文档

vue create bookblog

之后进入如下界面

Vue CLI v4.5.9
? Please pick a preset: (Use arrow keys)
> Default ([Vue 2] babel, eslint)Default (Vue 3 Preview) ([Vue 3] babel, eslint)Manually select features

可以选择Default默认配置,或是Manually手动配置。在此次项目中,直接使用Default默认即可。至于在Vue版本的选择上由于Vue 3的API文档还是beta版,因此我选择了Vue 2。在安装完成后就生成了bookblog项目文档。

bookblog
|-- README.md
|-- babel.config.js
|-- node_modules
|-- package-lock.json
|-- package.json
|-- public
`-- src

在项目文档生成后,即可使用vue框架正式开始BookBlog的页面的开发。为了更好地使用vue-cli,还需要了解学习vue的语法、单文件组件、npm、webpack等等相关知识(详情参看Vue官方网站)。在这些准备工作完成后,即可正式开始项目开发了。

2. book_blog_api包的使用

为了使用在上文中使用Swagger Editor生成的api接口book_blog_api包,需要在bookblog项目文档中进行npm install安装

npm install fentender/book_blog_api --save

在安装完成后即可在项目中调用该api包,以向服务端发送数据请求。具体用法如下:

import BookBlogApi from 'book_blog_api';var api = new BookBlogApi.BookApi()var bookId = 56; // {Number} Book's IDvar callback = function(error, data, response) {if (error) {console.error(error);} else {console.log('API called successfully. Returned data: ' + data);}
};
api.getBook(bookId, callback);

以下为本次项目中用于获得书籍信息时调用getBooks()接口的一次具体例子:

//Books.vue
import BookBlogApi from 'book_blog_api';var api = new BookBlogApi.BookApi();
var Books = null;export default {name: 'Books',data: function() {return {books: Books.books,num: Books.num,currentPage: 1}},...methods: {setData(err, data) {if(err) {this.error = err.toString();} else {this.books = data.books;this.num = data.num;}},},beforeRouteEnter(to, from, next) {if(!Books) {api.getBooks( {num: 1}, (err, data, response) => {let objif(response.body == null) {obj = JSON.parse(response.text);} else {obj = data;}Books = obj;next(vm => vm.setData(err, obj));})} else {next(vm => vm.setData(null, Books));}}, ...
}

3.使用mock拦截请求,模拟数据并返回

同样的,为了在项目中使用mockjs服务,需要先进行npm包的安装

npm install mockjs --save-dev    //由于mock测试只需要在开发环境进行,而在生成环境就不需要了,因此使用-dev

为了便于在完成客户端的开发后将mockjs测试关闭,独立创建一个文件用于保存mockjs测试代码。

//mock.js
import Mock from 'mockjs';
import ruler from './ruler'
// 请求模拟数据//Books
Mock.mock(/books$/, 'get', ruler.books);
Mock.mock(/books\/[0-9]+$/, 'get', ruler.book);
Mock.mock(/books\/[0-9]+$/, 'delete', null);...
...
...Mock.mock(/users\/[a-zA-Z0-9%]+\/bookshelfs\/[a-zA-Z0-9%]+\/[0-9]+$/, 'post', null);
Mock.mock(/users\/[a-zA-Z0-9%]+\/bookshelfs\/[a-zA-Z0-9%]+\/[0-9]+$/, 'delete', null);//Token
Mock.mock(/token$/, 'post', ruler.token);
Mock.mock(/token$/, 'get', ruler.token);//User
Mock.mock(/users\/[a-zA-Z0-9%]+$/, 'get', ruler.user);

ruler.js用于保存mockjs生成的模拟数据规范

import Mock from 'mockjs';var Random = Mock.Random;var exports = function() {this.book = {'bookId|+1' : 0,'bookName' : /[A-Z][a-z]{4,8}/,'autor' : /[a-z]{4,6} [a-z]{4,6}/,'info' : Random.paragraph(8, 15)}this.books = {'num|25-50' : 1,'books|10' : [this.book]};this.review = {'ID|+1' : 0,'Content' : Random.paragraph(8, 15),'autor' : /[a-z]{4,6} [a-z]{4,6}/};this.reviews = {'num|25-50' : 1,'reviews|10' : [this.review]};this.user = {'Username' : Random.word(),'Password' : Random.word(8, 10)};this.bookshelf = {'num|25-50' : 1,'bookshelf|8' : [{'bookName' : /[a-z]{4,6} [a-z]{4,6}/,'bookId|+1' : 0}]}this.bookshelfs = {'num|25-50' : 1,'bookshelfs|8' : [{'bookshelfName' : /[a-z]{4,6} [a-z]{4,6}/}]}this.token = {'Token' : Random.word(8)}
}export default new exports();

完成以上文件后,即可在main.js文件中通过添加与取消该文件的导入来控制mock测试的开启与否。

import './mock/mock.js';

4.客户端JWT认证

按照上文所述JWT设计思路:

  1. 客户端通过用户名和密码向服务器发送请求登陆
  2. 服务器收到请求数据,在数据库进行查询验证
  3. 如果验证成功,服务器签发一个Token给客户端
  4. 客户端可以将Token存放到SessionStroage 或者Cookie里
  5. 客服端设置监听,每次跳转路由,就判断 SessionStroage 中有无 Token ,没有就跳转到登录页面,有则跳转到对应路由页面
  6. 在每次客户端发送的请求中,在请求头中加上Token
  7. 在后端设置拦截器,用户登录后的每次请求都会经过这个拦截器校验Token是否有效
  8. 如果验证成功,则继续执行请求,返回请求到的数据

因此,为了实现JWT,首先需要在使用 login API成功登录客户端接收到Token后,需要将其存储在SessionStroage中。之后在每次跳转路由时,判断SessionStroage是否存在Token。并对于客户端的每个请求都进行拦截,在其请求头上加入Token。

为了储存Token在SeesionStorage,可以在调用signIn API的回调函数中,将数据存储在SessionStroage中。

//login.vue
...
...
api.signIn(userInfo, (err, data, response) => {if(response.statusCode == 200) {alert("登录成功!");let token;if(response.body) {token = data;} else {token = JSON.parse(response.text);}this.$store.commit("setToken", { token: token.Token });        //调用vuex的store状态中的setToken方法this.$store.commit("setUser", { username: this.username });this.$router.push({ name:"User", params: { username: this.username }});} else {alert("账号登录失败,请重新输入");}})
...
...//store/index.js
mutations: {setToken(state, token) {state.token = token.token;sessionStorage.setItem("token", state.token);            //储存token至sessionStorage中}, ...
}

之后的实现在路由跳转时判断token存在与否则可使用vue router的导航守卫beforeEach(to ,from, next)钩子函数来实现。该钩子函数会在每次路由跳转时被调用,from为跳转路由时的起始路由,to为跳转的目标路由,next用于继续管道中的下一个钩子。

router.beforeEach((to, from, next) => {if ( !to.meta.requiresAuth ) {next();} else {let token = sessionStorage.getItem("token");  //从sessionStorage取出字段token的值if( token === '' || token === null) {    //判断token是否存在alert("请先登录账号");next({ name: "Login"} )        //若不存在就跳转路由至登录界面} else {next();                        //若存在则继续管道中的下一个钩子,不对该路由跳转操作}}
})

最后,还需要在客户端的每一个发送请求的请求头上加入token,为了实现该功能,需要使用到Ajax-hook包,该包可以用于拦截浏览器XMLHttpRequest的库,并对其进行操作。

(其中需要注意的是,Ajax-hook与mockjs都会拦截XML HttpRequest请求,因此在同时使用这两者的功能时,两者会先后拦截客户端发送的请求并处理。这可能会造成一些错误,我暂时还没有解决。这时候的测试只能单独进行。)

官方案例:

import {proxy, unProxy} from "ajax-hook";
proxy({//请求发起前进入onRequest: (config, handler) => {console.log(config.url)handler.next(config);},//请求发生错误时进入,比如超时;注意,不包括http状态码错误,如404仍然会认为请求成功onError: (err, handler) => {console.log(err.type)handler.next(err)},//请求成功后进入onResponse: (response, handler) => {console.log(response.response)handler.next(response)}
})

通过使用该包,即可以实现出我们所需要的功能。

//http/http.js
import store from "../store";
import {proxy} from "ajax-hook";proxy({//请求发起前进入onRequest: (config, handler) => { if(store.state.token != ''){config.headers.Authorization = store.state.token        //在请求的headers中加入Authorization}handler.next(config);},//请求发生错误时进入onError: (err, handler) => {handler.next(err)},//请求成功后进入onResponse: (response, handler) => {handler.next(response)}
})

5.客户端结构

以下即为本次项目完成后的客户端src目录结构:

src
|-- App.vue
|-- assets
|   `-- imgs
|       |-- book.png
|       |-- bookshelf.png
|       |-- logo-mini.png
|       `-- logo.png
|-- components
|   |-- BookBrief.vue
|   |-- BookshelfBookBrief.vue
|   |-- BookshelfBrief.vue
|   |-- BookshelfFooter.vue
|   |-- Nav.vue
|   |-- PageFooter.vue
|   `-- ReviewBrief.vue
|-- http
|   `-- http.js
|-- main.js
|-- mock
|   |-- MockServer.js
|   |-- mock.js
|   `-- ruler.js
|-- router
|   `-- index.js
|-- store
|   `-- index.js
`-- views|-- Book.vue|-- Books.vue|-- Bookshelf.vue|-- Bookshelfs.vue|-- Home.vue|-- Login.vue|-- Review.vue|-- Reviews.vue|-- Signup.vue`-- User.vue

四、BookBlog服务端

本次项目服务端采用由Swagger自动生成的go-server为基础编写而成,使用Boltdb作为数据库,并借助negroni中间件实现jwt的验证。在最后使用postman进行测试。

Boltdb地址: https://github.com/boltdb/bolt

postman官网: https://www.postman.com/

negroni地址: https://github.com/urfave/negroni

1.API的编写

由于Swagger自动生成的go-server已经编写好了服务端的路由匹配功能,因此在服务端的工作只需要完成各个路由对应的API函数功能即可。以GetBooks为例:

//GetBooks 根据请求读取相应Book[]并返回
func GetBooks(w http.ResponseWriter, r *http.Request) {var i, j, pageNumber int = 0, 0, 1var book [30]Book = [30]Book{}if r.URL.Query()["pageNumber"] != nil {pageNumber, _ = strconv.Atoi(r.URL.Query()["pageNumber"][0])}db, err := bolt.Open("./database/bookblog.db", 0600, nil)if err != nil {log.Fatal(err)}defer db.Close()db.View(func(tx *bolt.Tx) error {b := tx.Bucket([]byte("Book"))c := b.Cursor()for k, v := c.First(); k != nil; k, v = c.Next() {if ((pageNumber-1)*10 > i || i >= pageNumber*10) && pageNumber != -1 {i++continue}json.Unmarshal(v, &book[j])j++i++}return nil})var buf []byteif pageNumber != -1 {buf, _ = json.Marshal(Books{int32(i), book[:10]})} else {buf, _ = json.Marshal(Books{int32(i), book[:]})}w.Header().Set("Content-Type", "application/json; charset=UTF-8")w.Header().Set("Access-Control-Allow-Origin", "*")w.Header().Set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")w.Header().Set("Access-Control-Allow-Headers", "x-requested-with,content-type")w.WriteHeader(http.StatusOK)w.Write(buf)
}

在完成GetBooks函数后,即可使用GET  /books。 按照以上例子,逐步将所有API填补实现即可。

2.Postman的使用

在编写完成API函数后即可使用Postman向服务端请求资源,以测试对应的功能。Postman的使用方法在其官网上已有详细的教程,通过简单的学习就可以开始使用了。

测试:GET /books

3.JWT的服务端实现

为了实现token验证,我们需要服务端每次接收请求后、正式处理请求前,在这两者之间进行token的验证。因此我们可以用到中间件negroni。它能够让我们添加中间件,以达到在API函数处理请求前验证token的功能。

首先,由于博客中并不是所有的功能都需要登录账号,因此只有一部分API需要验证token,因此对于每一个路由都为其添加requiredToken属性,通过设置它来控制其是否需要用到验证token的中间件。

type Route struct {Name          stringMethod        stringPattern       stringHandlerFunc   http.HandlerFuncrequiredToken bool
}

其次,在routers.go中修改原先的路由匹配

func NewRouter() *mux.Router {router := mux.NewRouter().StrictSlash(true)for _, route := range routes {var handler http.Handlerhandler = route.HandlerFunchandler = Logger(handler, route.Name)router.Methods(route.Method).Path(route.Pattern).Name(route.Name).Handler(handler)}return router
}

改为

func NewRouter() *mux.Router {router := mux.NewRouter().StrictSlash(true)for _, route := range routes {var handler http.Handlerhandler = route.HandlerFunchandler = Logger(handler, route.Name)//根据属性requiredToken判断是否需要使用中间件验证tokenif route.requiredToken {router.Methods(route.Method).Path(route.Pattern).Name(route.Name).Handler(negroni.New(negroni.HandlerFunc(authorizedValid), negroni.Wrap(handler)))} else {router.Methods(route.Method).Path(route.Pattern).Name(route.Name).Handler(handler)}}return router
}

最后完成authorizedValid中间件的编写,实现token验证

package swaggerimport ("github.com/boltdb/bolt""github.com/dgrijalva/jwt-go""github.com/dgrijalva/jwt-go/request""log""net/http"
)func authorizedValid(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {token, errors := request.ParseFromRequest(r, request.AuthorizationHeaderExtractor,func(token *jwt.Token) (interface{}, error) {return []byte(SecretKey), nil})db, err := bolt.Open("./database/bookblog.db", 0600, nil)if err != nil {log.Fatal(err)}if errors == nil && token.Valid {tokenString, _ := token.SignedString([]byte(SecretKey))if DbKeyofToken(db, tokenString) != nil {db.Close()log.Println("Token验证成功")next(rw, r)} else {db.Close()log.Println("Token验证失败")rw.Header().Set("Content-Type", "application/json; charset=UTF-8")rw.Header().Set("Access-Control-Allow-Origin", "*")rw.Header().Set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")rw.Header().Set("Access-Control-Allow-Headers", "x-requested-with,content-type")rw.WriteHeader(http.StatusUnauthorized)rw.Write([]byte("Token is not valid"))}} else {db.Close()log.Println("Token验证失败")rw.Header().Set("Content-Type", "application/json; charset=UTF-8")rw.Header().Set("Access-Control-Allow-Origin", "*")rw.Header().Set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")rw.Header().Set("Access-Control-Allow-Headers", "x-requested-with,content-type")rw.WriteHeader(http.StatusUnauthorized)rw.Write([]byte("Unauthorized access to this resource"))}
}

而JWT功能的测试也可通过Postman进行,只需要在请求中设置Authorization即可

五、前后端耦合

在完成前后端程序的编写后即可尝试将两者共同开启,测试其功能并进一步改善程序。然而在本次项目中前后端是独立部署,因此在测试时会遇到跨域的问题。

跨源资源共享(CORS): https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

我们可以通过修改服务端返回Header来解决此问题。

    w.Header().Set("Content-Type", "application/json; charset=UTF-8")w.Header().Set("Access-Control-Allow-Origin", "*")w.Header().Set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")w.Header().Set("Access-Control-Allow-Headers", "x-requested-with,content-type")w.WriteHeader(http.StatusOK)

除此之外,在本次项目中的一些请求可能会触发 CORS 预检请求。它会在正式发送请求前先发送一次OPTIONS类型的方法请求,以获知服务器是否允许该实际请求,从而避免跨域请求对服务器的用户数据产生未预期的影响。只有满足以下如有条件的请求才不会触发预检请求,也被称为简单请求。

  • 使用下列方法之一:

    • GET
    • HEAD
    • POST
  • 除了被用户代理自动设置的首部字段(例如 Connection ,User-Agent)和在 Fetch 规范中定义为 禁用首部名称 的其他首部,允许人为设置的字段为 Fetch 规范定义的 对 CORS 安全的首部字段集合。该集合为:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意额外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值仅限于下列三者之一:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

因此我们还需要创建一个处理OPTIONS的路由

Route{"OPTIONS",strings.ToUpper("options"),"/{all:[a-zA-Z0-9=\\-\\/]+}",Options,false,},
//Options Options请求处理
func Options(w http.ResponseWriter, r *http.Request) {w.Header().Set("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS")w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With,Content-Type,Authorization")w.Header().Set("Access-Control-Allow-Origin", "*")w.Header().Set("Content-Type", "application/json")w.WriteHeader(204)
}

六、项目展示

主页:

登录界面:

书架界面:(可自由创建删除且支持分页)

 书籍界面:

[服务计算] 简单 web 服务与客户端开发实战相关推荐

  1. 服务计算--简单 web 服务与客户端开发实战

    一.概述 利用 web 客户端调用远端服务是服务开发本实验的重要内容.其中,要点建立 API First 的开发理念,实现前后端分离,使得团队协作变得更有效率. 任务目标 选择合适的 API 风格,实 ...

  2. 测试开发——搭建一个简单 web服务(flask框架基础)项目实战

    搭建一个简单 web服务-flask框架 一.什么是wsgi? 二.搭建一个简单 web服务 三.扩展 四.请求加参数的情况 五.安装flask 一.什么是wsgi? wsgi是webserver和a ...

  3. java web服务_将Java服务公开为Web服务

    java web服务 本教程解决了开发人员面临的最实际的情况. 大多数时候,我们可能需要将某些现有服务公开为Web服务. 在项目生命周期的不同阶段可能会遇到这种情况. 如果这是初始阶段,那么您几乎是安 ...

  4. 将Java服务公开为Web服务

    本教程解决了开发人员面临的最实际的情况. 大多数时候,我们可能需要将某些现有服务公开为Web服务. 在项目生命周期的不同阶段可能会遇到这种情况. 如果这是初始阶段,那么您几乎是安全的,您可以为此做好充 ...

  5. Openlayers 地名搜索、坐标搜索、行政区划等服务-基于天地图Web服务

    Openlayers 地名搜索.坐标搜索.行政区划等服务-基于天地图Web服务 OpenLayers 教程 Openlayers 使用天地图Web服务 在线示例 OpenLayers 教程 天地图作为 ...

  6. SSR 学习 - 传统服务端渲染 Web 应用、客户端渲染、同构渲染、优缺点和案例演示

    概述 随着前端技术栈和工具链的迭代成熟,前端工程化.模块化也已成为了当下的主流技术方案. 在这波前端技术浪潮中,涌现了诸如 React.Vue.Angular 等基于客户端渲染的前端框架. 这类框架所 ...

  7. golang 简单web服务

    1.golang print输入 package mainimport "fmt"func main() {fmt.Printf("Hello World!\n" ...

  8. 无服务计算应用场景探讨及 FaaS 应用实战

    简介: 无服务计算本身是一个概念或者理论模型,落地到具体技术上主要有函数即服务(FaaS)以及后端即服务(BaaS)两种形式,阿里云提供函数即服务 FaaS 产品. 作者 | 宋文龙(闻可)  阿里云 ...

  9. 服务计算:简单的CLI程序

    Go Online传送门 Go Online 命令行程序是什么 CLI(Command Line Interface)实用程序是Linux下应用开发的基础.正确的编写命令行程序让应用与操作系统融为一体 ...

最新文章

  1. B站上线!DeepMind加UCL强强联手推出深度学习与强化学习进阶课程(附视频)
  2. CSS 居中完全指南
  3. pygame 移开的矩形留痕迹_Python之pygame学习矩形区域(5)
  4. overflowhidden用法思考
  5. ctb伺服驱动器说明书_青岛FANUC伺服电机364、453故障维修
  6. 机器学习生态全景图3.0
  7. Authorize.Net使用小结
  8. 未预期的符号 `( 附近有语法错误_苹果iOS 14.2现在提供117种新的表情符号和新的壁纸...
  9. “双态IT”架构下的自动化运维
  10. 第十三届蓝桥杯省赛 python B组复盘
  11. php 返回英文乱码,使用php 5时MySQL返回乱码的解决办法_php
  12. FPGA实现JPEG-LS图像压缩,有损无损可配置,提供工程源码和技术支持
  13. 手机系统更新(提示已是最新版本),怎么升级更新
  14. STFT和声谱图,梅尔频谱(Mel Bank Features)与梅尔倒谱(MFCCs)
  15. 傅里叶------傅里叶变换
  16. HPP结合PHP解析URL特性的SQL注入题目
  17. 2020年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总
  18. 如何使用 Arduino 制作一个绘图仪
  19. 六年级语文计算机个人研修计划,小学教师继续教育个人研修计划
  20. 安卓面试测试题复习专用 2019年版本个人收集

热门文章

  1. Oracle同义词的好处
  2. yum报Exiting on user cancel
  3. 我的AI之路(55)--如何获取kinetics数据集和如何制作自己的kinetics数据集
  4. 如何最快恢复逻辑备份
  5. PrimoCache2.2.0汉化程序与2.0.0的交叉使用
  6. Lyapunov stability analysis、LaSalle’s invariance principle、Barbalat’s lemma
  7. ElasticSearch 全文搜索引擎
  8. 安装python包的时候文件夹权限报错:InvalidArchiveError(“Error with archive D:\\anaconda\\pkgs\\cudnn-8.4.1.50-h)
  9. 【数独个人项目】2. 拿到题目之后
  10. Frida Internal - Part 1: 架构、Gum 与 V8