http实践

  • 基础
  • 表单传值
  • ajax
  • upload

基础

主要讲解ctx.body和content-type

核心代码处理

https://github.com/koajs/koa/blob/v2.x/lib/response.js#L129

  1. set body(val) {
  2. const original = this._body;
  3. this._body = val;
  4. if (this.res.headersSent) return;
  5. // no content
  6. if (null == val) {
  7. if (!statuses.empty[this.status]) this.status = 204;
  8. this.remove('Content-Type');
  9. this.remove('Content-Length');
  10. this.remove('Transfer-Encoding');
  11. return;
  12. }
  13. // set the status
  14. if (!this._explicitStatus) this.status = 200;
  15. // set the content-type only if not yet set
  16. const setType = !this.header['content-type'];
  17. // string
  18. if ('string' == typeof val) {
  19. if (setType) this.type = /^\s*</.test(val) ? 'html' : 'text';
  20. this.length = Buffer.byteLength(val);
  21. return;
  22. }
  23. // buffer
  24. if (Buffer.isBuffer(val)) {
  25. if (setType) this.type = 'bin';
  26. this.length = val.length;
  27. return;
  28. }
  29. // stream
  30. if ('function' == typeof val.pipe) {
  31. onFinish(this.res, destroy.bind(null, val));
  32. ensureErrorHandler(val, this.ctx.onerror);
  33. // overwriting
  34. if (null != original && original != val) this.remove('Content-Length');
  35. if (setType) this.type = 'bin';
  36. return;
  37. }
  38. // json
  39. this.remove('Content-Length');
  40. this.type = 'json';
  41. }

说明

  • HTTP 204(no content)表示响应执行成功,但没有数据返回,浏览器不用刷新,不用导向新页面。
  • 默认状态status = 200
  • type 根据body具体内容而定
    • string 返回html或text
    • buffer 返回bin
    • function 返回
    • 如果以上都是不是,默认为json,即对象

content-type

要学习content-type,必须事先知道它到底是什么,是干什么用的。

HTTP协议(RFC2616)采用了请求/响应模型。客户端向服务器发送一个请求,请求头包含请求的方法、URI、协议版本、以及包含请求修饰符、客户 信息和内容的类似于MIME的消息结构。服务器以一个状态行作为响应,相应的内容包括消息协议的版本,成功或者错误编码加上包含服务器信息、实体元信息以 及可能的实体内容。

通常HTTP消息由一个起始行,一个或者多个头域,一个只是头域结束的空行和可选的消息体组成。HTTP的头域包括通用头,请求头,响应头和实体头四个部分。每个头域由一个域名,冒号(:)和域值三部分组成。域名是大小写无关的,域 值前可以添加任何数量的空格符,头域可以被扩展为多行,在每行开始处,使用至少一个空格或制表符。

请求消息和响应消息都可以包含实体信息,实体信息一般由实体头域和实体组成。实体头域包含关于实体的原信息,实体头包括Allow、Content- Base、Content-Encoding、Content-Language、 Content-Length、Content-Location、Content-MD5、Content-Range、Content-Type、 Etag、Expires、Last-Modified、extension-header。
Content-Type是返回消息中非常重要的内容,表示后面的文档属于什么MIME类型。Content-Type: [type]/[subtype]; parameter。例如最常见的就是text/html,它的意思是说返回的内容是文本类型,这个文本又是HTML格式的。原则上浏览器会根据Content-Type来决定如何显示返回的消息体内容。

简单点讲:它就是告诉浏览器怎么样解析response内容

文本

stuq-koa-examples/koa-practice/http/content-type/string.js

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. // response
  4. app.use(ctx => {
  5. ctx.body = "plain string"
  6. });
  7. app.listen(3000);

Content Type String

注意此时的“Content-Type:text/plain; charset=utf-8”

这种用到的可能性是极其小的,大家了解一下即可

html

stuq-koa-examples/koa-practice/http/content-type/html.js

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. // response
  4. app.use(ctx => {
  5. ctx.body = "<h1>plain html<h1>"
  6. });
  7. app.listen(3000);

Content Type Html

Content-Type:text/html; charset=utf-8

很明显,返回html就在浏览器里渲染各种html标签,这是我们在浏览器里最常用的做法。所谓的网站等等也都是这样的。

json对象

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. // response
  4. app.use(ctx => {
  5. ctx.body = {
  6. "a":"1",
  7. "b": 2
  8. }
  9. });
  10. app.listen(3000);

Content Type Json

Content-Type:application/json; charset=utf-8

说明

返回json是api或者说前后端分离的常用方式。传输数据的方式一般是xml和json,但由于xml冗余等问题,除了编写web service外,绝大部分我们都会采用json这种轻量级的方式。

目前open api大部分也都是返回json数据的

更多见客户端 API 开发总结

使用模板渲染

我们再想想,如果返回的不是html string,而是模板呢?

模板引擎是一种复用思想,通过定义模板,用的时候和数据一起编译,生成html,以便浏览器渲染。从这个定义里我们可以找出几个关键点

编译(模板 + 数据) => html

举例

先来集成koa-views,其核心是consolidate.js,一个支持Node.js里大量模板引擎的库。

  1. $ npm i -S koa-views@next
  2. $ npm i -S pug

app.js

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. const views = require('koa-views')
  4. // Must be used before any router is used
  5. app.use(views(__dirname, { extension: 'pug' }))
  6. // response
  7. app.use(ctx => {
  8. return ctx.render('user', {
  9. user: 'John'
  10. });
  11. })
  12. app.listen(3000)

要点1: 引用库文件

  1. const views = require('koa-views')

要点2: 进行配置

  1. // Must be used before any router is used
  2. app.use(views(__dirname, { extension: 'pug' }))

要点3:使用ctx.render

  1. // response
  2. app.use(ctx => {
  3. ctx.render('dir/index', {
  4. a: 1
  5. })
  6. });
  • ‘dir/index’指的是模板文件位置
  • {a:1}是数据

那么render编译后会生成html,讲html string赋值给ctx.body,是不是就可以展示html了?

模板引擎有好多种,下面介绍2种典型的模板引擎

  • ejs:嵌入js语法的模板引擎(e = embed),类似于jsp,asp,erb的,在html里嵌入模板特性,如果熟悉html写起来就非常简单,只要区分哪些地方是可变,哪些地方是不变即可
  • jade:缩进式极简写法的模板引擎,发展历史 HAML -> Jade -> Slim -> Slm,最早是ruby里有的,目前以jade用的最多,这种写法虽好,,但需要大脑去转换,这其实是比较麻烦的,如果对html不是特别熟悉,这种思维转换是非常难受的。

更多见

总结

所有渲染都无外乎以下2种

  • 直接渲染: string 有2种文本和html,衍生出使用模板引擎编译生成html
  • 用做api: json object

其实了解html和koa的模板引擎如何渲染,就已经可以开发网站了。只有api开发,相对高级一点点,一般大些的项目才会使用的。

表单传值

上一节我们以表单为例介绍了HTTP请求,那么请求发到服务器了,我么该做什么呢?如何取参数呢?
koa2里的请求参数,都是在ctx.request上,这里主要讲get和post请求。

  • ctx.query === ctx.request.query
  • ctx.path === ctx.request.path
  • ctx.request.body

ctx.query

这里的query是querystring的别名

说明:ctx.query 不一定是get请求,因为querystring可以存在get或post请求里。

Koa最简单的获取querystring参数

创建文件 http/query/app.js

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. // response
  4. app.use(ctx => {
  5. ctx.body = 'Hello Koa-' + ctx.query['a'];
  6. });
  7. app.listen(3000);

注:ctx.query是ctx.request.query的别名,即ctx.query === ctx.request.query 。

启动服务器

  1. $ node query/app.js

然后访问http://127.0.0.1:3000/?a=1,此时页面显示“Hello Koa-1”,这里的1ctx.query['a']

提问

ctx.query只有get里可以用么?

根据ctx.path判断

如果我们想访问http://127.0.0.1:3000/topic?a=1呢?

http/query/app-2.js代码

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. // response
  4. app.use(ctx => {
  5. if (ctx.path === '/topic') {
  6. ctx.body = ' Hello Koa ' + ctx.path + ' a='+ ctx.query['a'];
  7. }
  8. ctx.body = ' Hello Koa with default path = ' + ctx.path ;
  9. });
  10. app.listen(3000);

启动服务器

  1. $ node query/app-2.js

访问http://127.0.0.1:3000/topic?a=1

返回Hello Koa /topic a=1

如果此时访问http://127.0.0.1:3000/?a=1呢?

返回Hello Koa with default path = /

总结一下

  • ctx.path 是请求的路径
  • ctx.query 获取的querystring
  • ctx.body 是返回浏览器页面的文本

/topic?a=1为例

  • ctx.path === ‘/topic’
  • ctx.query === ‘?a=1’

这样便于大家理解path和query的含义。

下面,我们想一下这里处理了2个请求,请求1是/topic,请求2是/topic以外的其他请求。如果我们再往极限一点想呢?比如有10个、100个请求怎么办?写一个无数个if/else么?

关于querystring的几种写法

  1. // GET /search?q=tobi+ferret
  2. ctx.query.q
  3. // => "tobi ferret"
  4. // GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
  5. ctx.query.order
  6. // => "desc"
  7. ctx.query.shoe.color
  8. // => "blue"
  9. ctx.query.shoe.type
  10. // => "converse"

因为有变态的写法

  1. // POST /search?q=tobi+ferret
  2. {a:1,b:2}
  3. ctx.query.q
  4. // => "tobi ferret"

post里看不到的,用ctx.request.body取。

ctx.request.body

ctx.request.body一定是post请求,因为get的请求头里没有request.body。并且在koa中没有内置,需要依赖的中间件bodyParser,不然ctx.request.body是没有的。
本章节使用的是koa2,所以需要安装koa-bodyparser@next模块

  1. npm install koa-bodyparser@next --save

包含在请求正文中提交的键值对数据,默认是undefined,当使用body-parsing中间件时ctx.request.body是内置在中间件中的,如 body-parser和multer中间件。

下面例子展示如何使用 body-parsing 中间件里的 ctx.request.body。
注意区分 ctx.body 和ctx.request.body 的区别,它们是完全不同的。

  1. const Koa = require('koa');
  2. const bodyParser = require('koa-bodyparser');
  3. const app = new Koa();
  4. app.use(bodyParser());
  5. // response
  6. app.use(ctx => {
  7. // the parsed body will store in this.request.body
  8. // if nothing was parsed, body will be an empty object {}
  9. console.log(ctx.request.body)
  10. ctx.body = ctx.request.body;
  11. });
  12. app.listen(3000);

当我们在浏览器里访问http://127.0.0.1:3000它的时候,是GET请求,此时ctx.request.body默认为空。

所以我们为了能够更清楚看到解析的post请求,所以我们使用chrome的postman插件查看

http实践 - 图4

此时可以看到返回值是

  1. {
  2. "a": "1"
  3. }

ctx.params(暂时不能用)

但可在router中使用:
新建app.js

  1. const Koa = require('koa');
  2. const router = require ('koa-router')();
  3. const app = new Koa();
  4. app.use(router.routes())
  5. .use(router.allowedMethods());
  6. router.get('/user/:id', function (ctx,next){
  7. ctx.body = 'user ' + ctx.params.id;
  8. });
  9. app.listen(3000);

启动服务器

  1. $ node app.js

然后访问http://127.0.0.1:3000/user/:id=10 会显示user :id=10

以html表单的方式测试get和post请求

get

创建服务器 (http/get/app.js):

  1. const Koa = require('koa');
  2. const bodyParser = require ('koa-bodyparser');
  3. const route = require('koa-router')();
  4. const app = new Koa();
  5. app.use(bodyParser());
  6. app.use(require('koa-static')(__dirname + '/public'));
  7. app.use(route.routes())
  8. .use(route.allowedMethods());
  9. route.get('/topic', function (ctx, next) {
  10. ctx.body = 'Hello koa' + ctx.query['a'];
  11. console.log (ctx.query['vehicle']);
  12. });
  13. app.listen(3000);

启动服务器

  1. $ node get/app.js

创建一段html表单代码(http/public/get.html):

  1. <form method="GET" action="/topic">
  2. <input type="text" name="a" value='1'><br><br>
  3. <input type="radio" name="sex" value="male">Male<br><br>
  4. <input type="radio" name="sex" value="female">Female<br><br>
  5. <input type="checkbox" name="vehicle" value="Bike">I have a bike<br><br>
  6. <input type="checkbox" name="vehicle" value="Car">I have a car<br><br>
  7. <input type="submit" value="OK">
  8. </form>

访问http://127.0.0.1:3000/get.html地址,点击OK按钮,会向’/topic’提交GET请求

http实践 - 图5

在shell中输出

  1. Car

从上图可以看出,表单里的get数据实际会在url里的querystring里,会显示出来。

post

创建服务器 (http/post/app.js):

  1. const Koa = require('koa');
  2. const bodyParser = require ('koa-bodyparser');
  3. const route = require('koa-router')();
  4. const app = new Koa();
  5. app.use(bodyParser());
  6. app.use(require('koa-static')(__dirname + '/public'));
  7. // routes definition
  8. app.use(route.routes())
  9. .use(route.allowedMethods());
  10. route.post('/toc/aaa', function (ctx, next) {
  11. ctx.body = ctx.request.body['a'];
  12. console.log (ctx.request.body['vehicle']);
  13. });
  14. app.listen(3000);

启动服务器

  1. $ node post/app.js

创建一段html表单代码(http/public/post.html):

  1. <form method="POST" action="/toc/aaa">
  2. <input type="text" name="a" value='1'><br><br>
  3. <input type="radio" name="sex" value="male">Male<br><br>
  4. <input type="radio" name="sex" value="female">Female<br><br>
  5. <input type="checkbox" name="vehicle" value="Bike">I have a bike<br><br>
  6. <input type="checkbox" name="vehicle" value="Car">I have a car<br><br>
  7. <input type="submit" value="OK">
  8. </form>

访问http://127.0.0.1:3000/post.html地址,会向’/toc/aaa’提交POST请求

http实践 - 图6

点击OK按钮

http实践 - 图7

从上图可以看出,表单里的post数据实际是不会在url里的querystring里。

上传

安装中间件

  1. $ npm install --save koa-multer

Koa中上传基本用法

创建上传文件存储目录:(http/uploads)
创建服务器 (http/app.js)

  1. const Koa = require('koa'); // v2
  2. const router = require('koa-router')(); // v6
  3. const multer = require('koa-multer');
  4. const app = new Koa();
  5. const upload = multer({ dest: 'uploads/' });
  6. app.use(require('koa-static')(__dirname + '/public'));
  7. app.use(router.routes())
  8. .use(router.allowedMethods());
  9. router.post('/profile', upload.single('upfiles'),function (ctx, next){
  10. ctx.body = "upload is success";
  11. });
  12. app.listen(3000);

see more https://github.com/koa-modules/multerhttps://github.com/expressjs/multer

启动服务器

  1. $ node app.js

创建一段html表单代码:(http/public/upload.html)

  1. <form method="POST" action="/profile" enctype='multipart/form-data'>
  2. 请选择上传的文件:<input type="file" name="upfiles">
  3. <input type="submit" value="OK">
  4. </form>

访问http://127.0.0.1:3000/upload.html地址

选择要上传的文件

http实践 - 图8

上传成功

http实践 - 图9http实践 - 图10

比较一下get和post请求

  • get更适合获取、搜索类的请求,url暴露在外面
  • post请求适合安全性更改,如创建xx等
  • 另外还有一点就是post能处理的表单内容比get要大非常多,后面会讲

Ajax异步请求

上一节讲了form传值,这是最基本的传值方式。这节我们讲一下我们最常用的ajax传值。
仍然按照我们之前讲过的

  • get/post/上传

什么是 ajax

Ajax即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术。核心是XMLHttpRequest对象(简称XHR),可以通过使用XHR对象获取到服务器的数据,然后再通过DOM将数据插入到页面中呈现。虽然名字中包含XML,但Ajax通讯与数据格式无关,所以我们的数据格式可以是XML或JSON等格式。

XMLHttpRequest对象用于在后台与服务器交换数据,具体作用如下:

  • 在不重新加载页面的情况下更新网页
  • 在页面已加载后从服务器请求数据
  • 在页面已加载后从服务器接收数据
  • 在后台向服务器发送数据

四步

  • 通过事件触发ajax请求
  • 发送ajax请求
  • 处理ajax请求结果,无论成功还是失败
  • 处理完成后,根据业务,对页面进行dom操作或css样式操作

比较传统表单和ajax异同

http实践 - 图11

TODO: 比较

示例helloworld演示

  1. $ cd book-source/http/ajax/helloworld
  2. $ ls
  3. ajax_info.txt index.html
  4. $ hs . -p 9090 -o
  5. Starting up http-server, serving .
  6. Available on:
  7. http://127.0.0.1:9090
  8. http://192.168.1.105:9090
  9. Hit CTRL-C to stop the server
  10. [Thu May 26 2016 22:32:54 GMT+0800 (CST)] "GET /" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"
  11. [Thu May 26 2016 22:32:55 GMT+0800 (CST)] "GET /favicon.ico" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36"
  12. [Thu May 26 2016 22:32:55 GMT+0800 (CST)] "GET /favicon.ico" Error (404): "Not found"

启动服务器命令说明

  • hs 是node模块http-server的简写命令,用于启动http服务器
  • -p 9090是设置端口的意思
  • -o 在默认浏览器里打开网址

源码说明

  • ajax_info.txt 文本文件,返回一段文字
  • index.html 所有的代码

源码解析

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset='uft-8' />
  5. <title> ajax hello world </title>
  6. </head>
  7. <body>
  8. <div id="demo"><h2>把AJAX返回的数据放到这里</h2></div>
  9. <button type="button" onclick="send_ajax_request()">改变内容</button>
  10. <script>
  11. function send_ajax_request() {
  12. var xhr = new XMLHttpRequest();
  13. xhr.onreadystatechange = function() {
  14. if (xhr.readyState == 4 && xhr.status == 200) {
  15. // dom
  16. document.getElementById("demo").innerHTML = xhr.responseText;
  17. // style
  18. document.getElementById('demo').style.background = "lightblue";
  19. }
  20. };
  21. xhr.open("GET", "ajax_info.txt", true);
  22. xhr.send();
  23. }
  24. </script>
  25. </body>
  26. </html>

四步骤要点

  • 通过事件触发ajax请求

给按钮增加onclick事件,在点击的时候调用send_ajax_request()

  1. <button type="button" onclick="send_ajax_request()">改变内容</button>
  • 发送ajax请求(此时是request发送)
  1. var xhr = new XMLHttpRequest();
  2. xhr.onreadystatechange = function() {
  3. if (xhr.readyState == 4 && xhr.status == 200) {
  4. ...
  5. }
  6. };
  7. xhr.open("GET", "ajax_info.txt", true);
  8. xhr.send();

在创建XHR对象后,接着我们要调用一个初始化方法open(),它接受五个参数具体定义如下:

  1. void open(
  2. DOMString method, //"GET", "POST", "PUT", "DELETE"
  3. DOMString url,
  4. optional boolean async,
  5. optional DOMString user,
  6. optional DOMString password
  7. );

这是完成的ajax请求代码,实际发送请求是通过send方法,即

  1. xhr.send();
  • 处理ajax请求结果,无论成功还是失败(此时是response处理)
  1. if (xhr.readyState == 4 && xhr.status == 200) {
  2. // dom
  3. document.getElementById("demo").innerHTML = xhr.responseText;
  4. // style
  5. document.getElementById('demo').style.background = "lightblue";
  6. }

onreadystatechange 事件

当请求被发送到服务器时,我们需要执行一些基于响应的任务。每当 readyState 改变时,就会触发 onreadystatechange 事件。readyState 属性存有 XMLHttpRequest 的状态信息。

readyState属性 存有 XMLHttpRequest 的状态。从 0 到 4 发生变化(
每个请求发送onreadystatechange 事件就会被触发 5 次(0 - 4),对应着 readyState 的每个变化。)

  • 0: 请求未初始化
  • 1: 服务器连接已建立
  • 2: 请求已接收
  • 3: 请求处理中
  • 4: 请求已完成,且响应已就绪

status是http状态码,给出常见的几种

  • 500 : ‘Internal Server Error服务器内部错误’,
  • 403 : ‘Forbidden禁止访问’,
  • 404 : ‘Not Found未找到页面’,
  • 304 : ‘Not Modified没有更改’,
  • 200 : ‘OK’,

在 onreadystatechange 事件中,我们规定当服务器响应已做好被处理的准备时所执行的任务。

当 readyState 等于 4 且状态为 200 时,表示响应已就绪,即此时你可以对response返回的数据或文本进行处理。

  • 处理完成后,根据业务,对页面进行dom操作或css样式操作

对页面进行dom操作

  1. document.getElementById("demo").innerHTML = xhr.responseText;

对页面进行css样式操作

  1. document.getElementById('demo').style.background = "lightblue";

问题

  • 连续点击【改变内容】按钮,为什么不再改变?
  • 为什么刷新会回到之前的内容?

这里response(服务器响应)处理的文本,那么用的最多的是什么呢?

如需获得来自服务器的响应,请使用 XMLHttpRequest 对象的 responseText 或 responseXML 属性。

  • xhr.responseText 获得字符串形式的响应数据。
  • xhr.responseXML 获得 XML 形式的响应数据。

可以任意类型,主要有

  • xml(使用xhr.responseXML)
  • text文本(xhr.responseText)
  • json(xhr.responseText)

虽然ajax里面的x是xml的意思,但实际情况xml用的极其的少,除了web service外,绝大部分情况我们会使用json作为服务端响应数据类型

一般讲,api开发(Application Programming Interface)泛指以返回json作为接口的服务端编程。

简单的json api示例

  1. $ cd book-source/http/ajax/json
  2. $ hs . -p 9091 -o
  3. $ ls
  4. data.json index.html

源码说明

  • data.json 文本文件,返回json对象
  • index.html 所有的代码

data.json

  1. {
  2. "content": "ajax_info里的数据"
  3. }

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset='uft-8' />
  5. <title> ajax with json </title>
  6. </head>
  7. <body>
  8. <div id="demo"><h2>把AJAX返回的数据放到这里</h2></div>
  9. <button type="button" onclick="send_ajax_request()">改变内容</button>
  10. <script>
  11. function send_ajax_request() {
  12. var xhr = new XMLHttpRequest();
  13. xhr.onreadystatechange = function() {
  14. if (xhr.readyState == 4 && xhr.status == 200) {
  15. // json parse
  16. var data = JSON.parse(xhr.responseText)
  17. // dom
  18. document.getElementById("demo").innerHTML = data.content;
  19. // style
  20. document.getElementById('demo').style.background = "lightblue";
  21. }
  22. };
  23. xhr.open("GET", "data.json", true);
  24. xhr.send();
  25. }
  26. </script>
  27. </body>
  28. </html>

相比较之前的text方式,差异如下

1)请求地址变了,是”data.json”

  1. xhr.open("GET", "data.json", true);

2)处理完成后,先解析xhr.responseText为json,对页面进行dom操作或css样式操作

  1. if (xhr.readyState == 4 && xhr.status == 200) {
  2. // 先解析xhr.responseText为json
  3. var data = JSON.parse(xhr.responseText)
  4. // dom
  5. document.getElementById("demo").innerHTML = data.content;
  6. // style
  7. document.getElementById('demo').style.background = "lightblue";
  8. }

其他操作都是一样的。

ajax与表单

上节讲了表单是用来页面之间传值用的,无论get还是post,它都会跳转到action对应的页面。而ajax是在当前页面就可以完成请求与响应,无需跳转,这是它们之间的差异。它们各自有各自的特点,比如

  • 有些页面是需要跳转的,比如登录、注册
  • 大部分页面为了有更好的体验,使用ajax,无刷新页面完成请求

那么ajax如何实现和表单一样的传值呢?

GET 还是 POST?

与 POST 相比,GET 更简单也更快,并且在大部分情况下都能用。

然而,在以下情况中,请使用 POST 请求:

  • 无法使用缓存文件(更新服务器上的文件或数据库)
  • 向服务器发送大量数据(POST 没有数据量限制)
  • 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

写一个接口

使用koa编写这个demo,用到2个模块就够了

1) 第一步npm init,会生成package.json文件

2) 安装依赖

  1. $ npm i -S koa@next
  2. $ npm i -S koa-static@next

在package.json里会自动增加2条依赖

  1. "dependencies": {
  2. "koa": "^2.0.0",
  3. "koa-static": "^3.0.0"
  4. }

3) 创建app.js

  1. $ touch app.js

在app.js里放入下面代码即可

  1. var serve = require('koa-static');
  2. var Koa = require('koa');
  3. var app = new Koa();
  4. // 启用静态httpserver
  5. app.use(serve(__dirname + '/public'));
  6. // 定义json接口
  7. app.use(ctx => {
  8. if (ctx.path === '/api/json') {
  9. ctx.body = {
  10. "content": "ajax_info里的数据"
  11. }
  12. } else {
  13. ctx.body = {
  14. "error": "请使用 /api/json 作为请求地址"
  15. }
  16. }
  17. });
  18. app.listen(3000);
  19. console.log('listening on port 3000');

说明

  • 启用静态httpserver
  • 定义了一个接口

4)启动、测试

  1. $ node app.js
  2. listening on port 3000

在浏览器里打开http://127.0.0.1:3000/api/json

返回如下

  1. // 20160527073015
  2. // http://127.0.0.1:3000/api/json
  3. {
  4. "content": "ajax_info里的数据"
  5. }

加上参数

get

get请求是通过querystring进行传值

  1. if (ctx.path === '/api/get_json_with_param') {
  2. console.log(ctx.query)
  3. var name = ctx.query.name
  4. ctx.body = {
  5. "content": "ajax_info里的数据",
  6. "name": name
  7. }
  8. }

http://127.0.0.1:3000/api/get_json_with_param?name=i5ting

post

koa默认是不支持post请求的,需要使用bodyparser模块

  1. npm i -S koa-bodyparser@next

修改app.js代码

  1. var serve = require('koa-static');
  2. var bodyParser = require('koa-bodyparser');
  3. var Koa = require('koa');
  4. var app = new Koa();
  5. // 处理post请求
  6. app.use(bodyParser());
  7. // 启用静态httpserver
  8. app.use(serve(__dirname + '/public'));

下面来看一下post请求代码如何处理

  1. if (ctx.path === '/api/post_json_with_param') {
  2. console.log(ctx.request.body)
  3. var name = ctx.request.body.name
  4. ctx.body = {
  5. "content": "ajax_info里的数据",
  6. "name": name
  7. }
  8. }

get请求可以在浏览器里显示的测试,而post是不能直接通过url测试的,所以为了简便,我们这里使用chrome的插件postman测试。注意post的类型是x-www-form-urlencoded。

http实践 - 图12

总结

至此,我们把koa的get和post返回json api的代码就都讲完了,有了这些api,我们就可以尝试ajax和json api进行联调。

联调

get

点击 【发送get请求】按钮触发get请求

  1. <button type="button" onclick="send_get_request()">发送get请求</button>

如果您希望通过 GET 方法发送信息,请向 URL 添加信息:

  1. function send_get_request() {
  2. var xhr = new XMLHttpRequest();
  3. xhr.onreadystatechange = function() {
  4. if (xhr.readyState == 4 && xhr.status == 200) {
  5. // json parse
  6. var data = JSON.parse(xhr.responseText)
  7. // dom
  8. document.getElementById("demo").innerHTML = data.content;
  9. // style
  10. document.getElementById('demo').style.background = "green";
  11. }
  12. };
  13. xhr.open("GET", "/api/get_json_with_param?name=i5ting", true);
  14. xhr.send();
  15. }

post

点击 【发送post请求】按钮触发post请求

  1. <button type="button" onclick="send_post_request()">发送post请求</button>

如果需要像 HTML 表单那样 POST 数据,请使用 setRequestHeader() 来添加 HTTP 头。然后在 send() 方法中规定您希望发送的数据:

  1. function send_post_request() {
  2. var xhr = new XMLHttpRequest();
  3. xhr.onreadystatechange = function() {
  4. if (xhr.readyState == 4 && xhr.status == 200) {
  5. // json parse
  6. var data = JSON.parse(xhr.responseText)
  7. // dom
  8. document.getElementById("demo").innerHTML = data.content;
  9. // style
  10. document.getElementById('demo').style.background = "red";
  11. }
  12. };
  13. xhr.open("POST", "/api/post_json_with_param", true);
  14. xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  15. xhr.send("name=i5ting");
  16. }

demo

http://127.0.0.1:3000/test.html

表单取值

此时我们的请求还是没有任何输入的,因为我们都给固定死了,这其实是为了演示方便,但绝大部分情况是不会这样的,都有正常的输入。

结合我们讲的ajax流程的4个步骤,想想,如果动态取表单里的值,然后ajax,应该是什么样的流程?

ajax完整5步骤

  • 通过事件触发ajax请求
  • 通过dom获取表单数据值
  • 组装表单数据值,发送ajax请求
  • 处理ajax请求结果,无论成功还是失败
  • 处理完成后,根据业务,对页面进行dom操作或css样式操作

这里以get请求举例

  1. function send_get_request() {
  2. var xhr = new XMLHttpRequest();
  3. xhr.onreadystatechange = function() {
  4. if (xhr.readyState == 4 && xhr.status == 200) {
  5. // json parse
  6. var data = JSON.parse(xhr.responseText)
  7. // dom
  8. document.getElementById("demo").innerHTML = "get返回的结果" + data.name;
  9. // style
  10. document.getElementById('demo').style.background = "green";
  11. }
  12. };
  13. var myname = document.getElementById("myname").value
  14. xhr.open("GET", "/api/get_json_with_param?name=" + myname, true);
  15. xhr.send();
  16. }

说明

1)dom获取表单数据

  1. var myname = document.getElementById("myname").value

2) 组装ajax要传送的表单数据

  1. xhr.open("GET", "/api/get_json_with_param?name=" + myname, true);

这里是get,所以在querystring里组装

如果是post

  1. function send_post_request() {
  2. var xhr = new XMLHttpRequest();
  3. xhr.onreadystatechange = function() {
  4. if (xhr.readyState == 4 && xhr.status == 200) {
  5. // json parse
  6. var data = JSON.parse(xhr.responseText)
  7. // dom
  8. document.getElementById("demo").innerHTML = "post返回的结果" + data.name;
  9. // style
  10. document.getElementById('demo').style.background = "red";
  11. }
  12. };
  13. xhr.open("POST", "/api/post_json_with_param", true);
  14. xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  15. var myname = document.getElementById("myname").value
  16. xhr.send("name=" + myname);
  17. }

1) 需要设置xhr.setRequestHeader

  1. xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");

2)表单传值不在querystring,而是直接放到send方法里,写法和querystring一样。

  1. xhr.send("name=" + myname);

例子book-source/http/ajax/koa-json/public/test.html ,请访问 http://127.0.0.1:3000/form.html

jQuery ajax

通过 jQuery AJAX 方法,您能够使用 HTTP Get 和 HTTP Post 从远程服务器上请求文本、HTML、XML 或 JSON - 同时您能够把这些外部数据直接载入网页的被选元素中。
如果没有 jQuery,AJAX 编程还是有些难度的。
编写常规的 AJAX 代码并不容易,因为不同的浏览器对 AJAX 的实现并不相同。这意味着您必须编写额外的代码对浏览器进行测试。不过,jQuery 团队为我们解决了这个难题,我们只需要一行简单的代码,就可以实现 AJAX 功能。

在使用Ajax前,需要下载jQuery库,并在页面中引入<script src="jquery.js"></script>

我们这里使用最多的jQuery库举例

  1. $.get("test.cgi", { name: "John", time: "2pm" }, function( data ) {
  2. // 处理ajax请求结果
  3. alert( "Data Loaded: " + data );
  4. // 根据业务,对页面进行dom操作或css样式操作
  5. $(sss).html().css()
  6. });
  • 使用$.get或$.post发送ajax请求。可以理解它是对原生的xhr封装
  • 在回调中function( data ) {}里处理ajax请求结果
  • $(sss).html().css()对页面进行dom操作或css样式操作

https://github.com/DevMountain/mini-ajax

jQuery Ajax使用方法与 Ajax 相似,写接口、创建服务器、启动服务器,与Ajax相同,这里不再重复。

给出app.js 源码

  1. var koa = require ('koa');
  2. var serve = require ('koa-static');
  3. var bodyParser = require ('koa-bodyparser');
  4. var app = new koa();
  5. app.use (bodyParser());
  6. app.use (serve(__dirname + '/public'));
  7. app.use ( ctx => {
  8. if(ctx.path === '/api/get_json_with_param'){
  9. console.log(ctx.query);
  10. var name = ctx.query.name;
  11. ctx.body = {
  12. "content":"ajax_info里的数据",
  13. "name": name
  14. }
  15. } else if (ctx.path === '/api/post_json_with_param') {
  16. console.log(ctx.request.body)
  17. var name = ctx.request.body.name
  18. ctx.body = {
  19. "content": "post_json_with_param里的数据",
  20. "name": name
  21. }
  22. } else {
  23. ctx.body = {
  24. "error":"请使用 /api/json 作为请求地址"
  25. }
  26. }
  27. });
  28. app.listen(3000);
  29. console.log ("listening on port 3000");

启动服务器

  1. $node app.js

以表单为例,介绍如何使用$.get或$.post发送ajax请求

同样5步骤

  • 通过事件触发ajax请求
  • 通过dom获取表单数据值
  • 组装表单数据值,发送ajax请求
  • 处理ajax请求结果,无论成功还是失败
  • 处理完成后,根据业务,对页面进行dom操作或css样式操作

引入jQuery 库

  1. <head>
  2. <meta charset='uft-8' />
  3. <title> ajax with json </title>
  4. <script src="/script/jquery.js"></script>
  5. </head>

get

$.get(url,[data],[callback])

  • url (String) 发送请求的URL地址.
  • data (Map)(可选参数) 要发送给服务器的数据,以 Key/value 的键值对形式表示,会做为QueryString附加到请求URL中
  • callback (Callback) (可选参数) 载入成功时回调函数(只有当Response的返回状态是success才是调用该方法),该函数接受两个参数,第一个为服务器返回的数据,第二个参数为服务器的状态。
  1. <form>
  2. <input type='text' name='username' value='i5ting' id='myname'/>
  3. <div id="demo"><h2>把AJAX返回的数据放到这里</h2></div>
  4. <button id="bt1" type="button">发送get请求</button>
  5. <button id="bt2" type="button">发送post请求</button>
  6. </form>
  7. <script>
  8. $('#bt1').click( function () { // 触发ajax请求
  9. var name1 = $("#myname").val(); // 获取表单数据值
  10. console.log(name1); // 打印取到的表单值
  11. $.get ('/api/get_json_with_param', {name:name1}, function (data,status) {
  12. // function (data,staus)中,data为服务器返回数据,status为服务器状态
  13. $('#demo').html("get返回的结果" + data.name).css('background','lightblue');
  14. console.log (data); // 打印返回数据
  15. });
  16. });
  17. </script>

post

$.post(url,[data],[callback],[type])

这个函数跟$.get()参数差不多。

  • url (String) 发送请求的URL地址.
  • data (Map)(可选参数) 要发送给服务器的数据,以 Key/value 的键值对形式表示
  • callback (Callback) (可选参数) 载入成功时回调函数(只有当Response的返回状态是success才是调用该方法)
  • type (String) (可选参数) 请求数据的类型,xml,text,json等,如果我们设置这个参数为:json,那么返回的格式则是json格式的,如果没有设置,就 和$.get()返回的格式一样,都是字符串的
  1. <form>
  2. <input type='text' name='username' value='i5ting' id='myname'/>
  3. <div id="demo"><h2>把AJAX返回的数据放到这里</h2></div>
  4. <button id="bt1" type="button">发送get请求</button>
  5. <button id="bt2" type="button">发送post请求</button>
  6. </form>
  7. <script>
  8. $('#bt2').click( function () {
  9. var name1 = $("#myname").val();
  10. console.log(name1);
  11. $.post ('/api/post_json_with_param', {name:name1},function (data,status) {
  12. $('#demo').html("post返回的结果" + data.name).css('background','red');
  13. console.log (data); // 打印返回数据
  14. });
  15. });
  16. </script>

upload

增加router

  1. npm i -S koa-router@next
  2. npm install --save koa-multer

具体代码

  1. const Koa = require('koa')
  2. const app = new Koa()
  3. var router = require ('koa-router')();
  4. const views = require('koa-views')
  5. const multer = require('koa-multer');
  6. const upload = multer({ dest: 'uploads/' });
  7. // Must be used before any router is used
  8. app.use(views(__dirname, { extension: 'pug' }))
  9. router.post('/profile', upload.single('avatar'), ctx => {
  10. return ctx.render('user', {
  11. user: 'sucess upload file'
  12. });
  13. });
  14. app
  15. .use(router.routes())
  16. .use(router.allowedMethods());
  17. app.listen(3000)

要实现上传功能,需要借助插件。
推荐koa-uploadify
https://github.com/i5ting/uploadify/tree/revert-2-koa-uploadify
可以把源码下载下来研究一下。

总结

使用jQuery Ajax 相对简单,易于理解,而且它要小好多。