从零学习node.js之搭建http服务器(二)


Posted in Javascript onFebruary 21, 2017

前言

在上篇文章中我们了解了一下不同模块规范之间的联系与区别。本文我们正式开始node的学习,首先我们从搭建一个http服务器,能运行简单的程序开始说起。

一、hello world

最经典的hello world。首先我们创建一个server.js来保存我们的代码:

console.log( 'hello world' );

在终端输入node server.js运行:

node server.js

终端就会输出 hello world 的字样。可是我们一个node服务器程序,总是要在浏览器上访问的呀,这里就要用到node里自带的http模块了:

var http = require('http'); // 引入http模块

// 创建http服务器
// request : 从浏览器带来的请求信息
// response : 从服务器返回给浏览器的信息
http.createServer(function(request, response){
 response.writeHead(200, {'content-type': 'text/plain'}); // 设置头部信息,输出text文本
 response.write('hello world'); // 输出到页面中的信息
 response.end(); // 返回结束
}).listen(3000);
console.log('server has started...');

我们再次在终端输入node server.js运行,终端里会有输出 server has started… 的字样,表示服务器已创建并正在运行,然后我们在浏览器上访问127.0.0.1:3000,就可以看到页面中输出了hello world。

二、form表单

刚才我们只是在页面中输出了一段简单的文本,现在我们要在页面中呈现一个表单,可以让用户输入信息并进行提交:

// server.js
var http = require('http');

http.createServer(function(request, response){
var html = '<html>\
 <head>\
 <meta charset=UTF-8" />\
 </head>\
 <body>\
 <form action="/" method="post">\
 <p>username : <input type="text" name="username" /></p>\
 <p>password : <input type="password" name="password" /></p>\
 <p>age : <input type="text" name="age" /></p>\
 <p><input type="submit" value="submit" name="submit" /></p>\
 </form>\
 </body>\
 </html>';

 response.writeHead(200, {'content-type': 'text/html'}); // 输出html头信息
 response.write(html); // 将拼接的html字符串输出到页面中
 response.end(); // 结束
}).listen(3000);
console.log('server has started...');

修改server.js中的内容,重新运行:

node server.js

刷新页面后,我们发现页面中输出了3个文本框和1个提交按钮。因为我们的程序只是呈现页面,并没有做任何其他的处理,因此在页面中提交数据只是刷新当前页面。

注意: 我们每次修改node中的任何代码后,都要重新进行启动。

2.1 获取表单GET方式提交的数据

我们上面的代码中使用的是POST方式,不过这里要先讨论使用GET方式提交过来的数据,我们先不考虑数据的安全性,只是学习如何获取使用get方式提交过来的form表单数据,将post改为get,重新运行。

我们知道,使用get方式提交数据,会将数据作为URL参数传递过来,因此我们通过解析URL中的参数获取到数据,这里就用到了url模块中的方法:

// server.js
var http = require('http'),
url = require('url');

http.createServer(function(request, response){
 var html = '<html>\
 <head>\
 <meta charset=UTF-8" />\
 </head>\
 <body>\
 <form action="/" method="get">\
 <p>username : <input type="text" name="username" /></p>\
 <p>password : <input type="password" name="password" /></p>\
 <p>age : <input type="text" name="age" /></p>\
 <p><input type="submit" value="submit" name="submit" /></p>\
 </form>\
 </body>\
 </html>';
 
 var query = url.parse( request.url, true ).query;
 if( query.submit ){
 var data = '<p><a href="/" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >back</a></p>'+
 '<p>username:'+query.username+'</p>'+
 '<p>password:'+query.password+'</p>'+
 '<p>age:'+query.age+'</p>';
 
 response.writeHead(200, {'content-type': 'text/html'});
 response.write(data);
 }else{
 response.writeHead(200, {'content-type': 'text/html'});
 response.write(html);
 }
 response.end(); // 结束
}).listen(3000);
console.log('server has started...');

我们再次运行提交后就能在页面中显示出数据了。

url.parse是用来解析URL字符串的,并返回解析后的URL对象。若我们只输出一下 url.parse(request.url)

url.parse(request.url);

result:
Url {
 protocol: null,
 slashes: null,
 auth: null,
 host: null,
 port: null,
 hostname: null,
 hash: null,
 search: '?username=111113&password=123&age=122&submit=submit',
 query: 'username=111113&password=123&age=122&submit=submit',
 pathname: '/',
 path: '/?username=111113&password=123&age=122&submit=submit',
 href: '/?username=111113&password=123&age=122&submit=submit'
}

如果将第2个参数设置为true,则会将返回结果中的query属性解析为一个对象,其他属性不变;默认值为false,即query属性是一个字符串:

url.parse(request.url, true);

result:
Url {
...
query: {
 username: '111113',
 password: '123',
 age: '122',
 submit: 'submit' },
...
}

因此我们可以通过如下语句判断是否有提交数据并获取提交数据,然后再输出到中即可:

var query = url.parse( request.url, true ).query;
/*
{
 username: '111113',
 password: '123',
 age: '122',
 submit: 'submit'
}
*/

2.2 获取表单POST方式提交的数据

现在我们使用post方式来提交数据。因为POST请求一般都比较“重” (用户可能会输入大量的内容),如果用阻塞的方式来处理处理,必然会导致用户操作的阻塞。因此node将post数据拆分为很多小的数据块,然后通过data事件(表示新的小数据块到达了)和end事件传递这些小数据块(表示所有的数据都已经接收完毕)。 所以,我们的思路应该是:在data事件中获取数据块,在end事件中操作数据。

// server.js
var http = require('http'),
querystring = require('querystring');

http.createServer(function(request, response){
 var html = '<html>\
 <head>\
 <meta charset=UTF-8" />\
 </head>\
 <body>\
 <form action="/" method="post">\
 <p>username : <input type="text" name="username" /></p>\
 <p>password : <input type="password" name="password" /></p>\
 <p>age : <input type="text" name="age" /></p>\
 <p><input type="submit" value="submit" name="submit" /></p>\
 </form>\
 </body>\
 </html>';
 
 if( request.method.toLowerCase()=='post' ){
 var postData = '';

 request.addListener('data', function(chunk){
 postData += chunk;
 });

 request.addListener('end', function(){
 var data = querystring.parse(postData);
 console.log( 'postData: '+postData );
 console.log(data);
 
 var s = '<p><a href="/" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >back</a></p>'+
 '<p>username:'+data.username+'</p>'+
 '<p>password:'+data.password+'</p>'+
 '<p>age:'+data.age+'</p>';

 response.writeHead(200, {'content-type': 'text/html'});
 response.write(s);
 response.end();
 })
 }else{
 response.writeHead(200, {'content-type': 'text/html'});
 response.write(html);
 response.end();
 }
}).listen(3000);
console.log('server has started...');

这段代码与上段代码项目,主要有的几个变化是:

  1. 不再引入url模块, 改用引入querystring模块。因为我们不再对URL进行操作了,也没必要引入了;
  2. 使用request.method.toLowerCase()=='post'判断当前是否有数据提交;
  3. 在data事件进行数据的拼接,在end事件中进行的处理;
  4. response.end()写在了end事件内部,因为end事件是异步操作,因此必须得数据输出完成之后才能执行response.end()

我们在控制台中可以看出,postData是这样的一个字符串:

'username=123&password=123&age=23&submit=submit';

因此我们使用query.parse将postData解析为对象类型,以便获取提交过来的数据。

三、路由

现在我们所有的逻辑都是在根目录下进行的,没有按照url区分,这里我们按照功能进行路由拆分。以上面的post请求为例,我们可以拆分为:页面初始化和form提交后的处理。

页面初始化:

// starter.js 页面初始化

function start(request, response){
 var html = '<html>\
 <head>\
 <meta charset=UTF-8" />\
 </head>\
 <body>\
 <form action="/show" method="post">\
 <p>username : <input type="text" name="username" /></p>\
 <p>password : <input type="password" name="password" /></p>\
 <p>age : <input type="text" name="age" /></p>\
 <p><input type="submit" value="submit" name="submit" /></p>\
 </form>\
 </body>\
 </html>';
 
 response.writeHead(200, {"Content-Type":"text/html"});
 response.write( html );
 response.end();
}
exports.start = start;

展示获取的数据:

// uploader.js 展示获取的数据
var querystring = require('querystring');

function upload(request, response){
 var postData = '';

 request.addListener('data', function(chunk){
 postData += chunk;
 });
 
 request.addListener('end', function(){
 var data = querystring.parse(postData);
 console.log( 'postData: '+postData );
 console.log(data);

 var s = '<p><a href="/" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >back</a></p>'+
 '<p>username:'+data.username+'</p>'+
 '<p>password:'+data.password+'</p>'+
 '<p>age:'+data.age+'</p>';

 response.writeHead(200, {'content-type': 'text/html'});
 response.write(s);
 response.end();
 })
}
exports.upload = upload;

然后在server.js中进行路由选择

// server.js
var http = require('http'),
url = require('url');

http.createServer(function(request, response){
 var pathname = url.parse(request.url).pathname;
 console.log(pathname);
 response.end();
}).listen(3000);
console.log('server has started...');

我们任意改变URL地址,会看到输出的每个地址的pathname(忽略/favicon.ico):

http://127.0.0.1:3000/ // 输出: /
http://127.0.0.1:3000/show/ // 输出: /show/
http://127.0.0.1:3000/show/img/ // 输出: /show/img/
http://127.0.0.1:3000/show/?username=wenzi // 输出: /show/

因此我们就根据pathname进行路由,对路由进行方法映射:

// server.js
var http = require('http'),
url = require('url'),
starter = require('./starter'),
uploader = require('./uploader');

http.createServer(function(request, response){
 var pathname = url.parse(request.url).pathname;
 var routeurl = {
 '/' : starter.start,
 '/show' : uploader.upload
 }

 if( typeof routeurl[pathname]=== 'function' ){
 routeurl[pathname](request, response);
 }else{
 console.log('404 not found!');
 response.end();
 }
}).listen(3000);
console.log('server has started...');

如果匹配到路由 / ,则执行 starter.start(request, response) ;如果匹配到路由 /show ,则执行 uploader.upload(request, response) 。如果都没匹配到,则显示404。

四、 图片上传并显示

在上面我们已经能成功提交数据了,这里来讲解如何进行图片上传并显示。使用node自带的模块处理起来非常的麻烦,这里我们使用别人已经开发好的formidable模块进行编写,它对解析上传的文件数据做了很好的抽象。

npm install formidable --save-dev

在starter.js中,我们添加上file控件:

// starter.js
function start(request, response){
 var html = '<html>\
 <head>\
 <meta charset=UTF-8" />\
 </head>\
 <body>\
 <form action="/upload" method="post" enctype="multipart/form-data">\
 <p>file : <input type="file" name="upload" multiple="multiple" /></p>\
 <p><input type="submit" value="submit" name="submit" /></p>\
 </form>\
 </body>\
 </html>';

 response.writeHead(200, {"Content-Type":"text/html"});
 response.write( html );
 response.end();
}
exports.start = start;

4.1 图片上传

首先我们进行的是图片上传操作,首先我们要确保当前目录中存在tmp和img目录。

在 uploader.js 中:

// uploader.js
var formidable = require('formidable'),
util = require('util'),
fs = require('fs');

function upload(request, response){
 if( request.method.toLowerCase()=='post' ){
 var form = new formidable.IncomingForm();

 form.uploadDir = './tmp/';
 form.parse(request, function(err, fields, files) {
 var oldname = files.upload.name,
 newname = Date.now() + oldname.substr(oldname.lastIndexOf('.'));
 fs.renameSync(files.upload.path, "./img/"+newname ); // 上传到 img 目录

 response.writeHead(200, {'content-type': 'text/plain'});
 response.write('received upload:\n\n');
 response.end(util.inspect({fields: fields, files: files}));
 });
 return;
 }
}
exports.upload = upload;

我们上传图片后跳转到upload路径,然后显示出相应的信息:

received upload:

{
 fields: { // 其他控件,如input, textarea等
 submit: 'submit'
},
files:{ // file控件
 upload:{
 domain: null,
 _events: {},
 _maxListeners: undefined,
 size: 5097,
 path: 'tmp\\upload_b1f7c3e83af224e9f3a020958cde5dcd',
 name: 'chrome.png',
 type: 'image/png',
 hash: null,
 lastModifiedDate: Thu Jan 12 2017 23:09:50 GMT+0800 (中国标准时间),
 _writeStream: [Object]
 }
 }
}

我们再查看img目录时,就会发现我们刚才上传的照片了。

4.2 图片显示

将图片上传到服务器后,怎样才能把图片显示在浏览器上呢。这里我们就使用到了fs模块来读取文件,创建一个shower.js来专门展示图片:

// shower.js
var fs = require('fs'),
url = require('url');

function show(request, response){
 var query = url.parse(request.url, true).query,
 imgurl = query.src;

 // 读取图片并进行输出
 // 这里读取链接中的src参数,指定读取哪张图片 /show?src=1484234660592.png
 fs.readFile('./img/'+imgurl, "binary", function(err, file){
 if(err) throw err;
 response.writeHead(200, {"Content-Type": "image/png"});
 response.write(file, "binary");
 response.end();
 })
}
exports.show = show;

然后在 server.js 中添加上 show 的路由映射:

var routeurl = {
 '/' : starter.start,
 '/upload' : uploader.upload,
 '/show' : shower.show // 添加
};

最后在 upload.js 中进行图片的引用:

form.parse(request, function(err, fields, files) {
 var oldname = files.upload.name,
 newname = Date.now() + oldname.substr(oldname.lastIndexOf('.'));
 fs.renameSync(files.upload.path, "./img/"+newname ); // 同步上传图片

 response.writeHead(200, {'content-type': 'text/html'});
 var s = '<p><a href="/" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" >back</a></p><p><img src="/show?src='+newname+'" /></p>'; // 显示刚才的图片
 response.write(s);
 response.end();
});

五、综合

刚才学习了上传数据和上传图片,这里我们将其综合一下,拟定一个题目:“设定用户名密码,并上传头像”。希望可以自己实现一下。

六、接口的实现

在第2部分学习了GET和POST请求,那么在这里写一个简单json或jsonp接口应该不是什么难事儿了吧。

创建一个 inter.js :

// inter.js
var url = require('url');

function init(request, response){
 if( request.method.toLowerCase()=='get' ){
 var query = url.parse(request.url, true).query;

 var data = {"code":0, "msg":"success", "data":[{"username":"wenzi", "age":26}, {"username":"bing", "age":25}]};
 if( query && query.callback ){
 // jsonp
 response.end( query.callback + '(' + JSON.stringify(data) + ')' );
 }else{
 // json
 response.end( JSON.stringify(data) );
 }
 }
}
exports.init = init;

在server中添加inter的引用和路由映射:

var routeurl = {
 '/' : starter.start,
 '/upload' : uploader.upload,
 '/show' : shower.show,
 '/inter' : inter.init // 添加
};

然后对 http://127.0.0.1:3000/inter 进行json请求或jsonp请求即可。

为大家提供一个查询API的中文地址:https://pinggod.gitbooks.io/nodejs-doc-in-chinese/content

总结

好了,以上就是这篇文章的全部内容了,这节还是写了不少的内容,最核心的就是讲解如何搭建一个简单的http服务器,进行数据和图片的提交与处理,在最后稍微讲了下接口的编写,后面有机会的话,会再具体讲解下接口的编写。下面会和大家分享node之文件操作,请大家继续关注三水点靠木。

Javascript 相关文章推荐
TBCompressor js代码压缩
Jan 05 Javascript
TimergliderJS 一个基于jQuery的时间轴插件
Dec 07 Javascript
jQuery检测输入的字符串包含的中英文的数量
Apr 17 Javascript
jQuery简单实现title提示效果示例
Aug 01 Javascript
Javascript 闭包详解及实例代码
Nov 30 Javascript
jQuery布局组件EasyUI Layout使用方法详解
Feb 28 Javascript
Vue表单类的父子组件数据传递示例
May 03 Javascript
微信小程序使用swiper组件实现类3D轮播图
Aug 29 Javascript
vue-cli3全面配置详解
Nov 14 Javascript
JavaScript实现学生在线做题计时器功能
Dec 05 Javascript
详解用Webpack与Babel配置ES6开发环境
Mar 12 Javascript
js实现盒子拖拽动画效果
Aug 09 Javascript
jQuery动态生成不规则表格(前后端)
Feb 21 #Javascript
jquery设置css样式的多种方法(总结)
Feb 21 #Javascript
JavaScript中 this 指向问题深度解析
Feb 21 #Javascript
js return返回多个值,通过对象的属性访问方法
Feb 21 #Javascript
从零学习node.js之模块规范(一)
Feb 21 #Javascript
JS获得一个对象的所有属性和方法实例
Feb 21 #Javascript
JS中实现函数return多个返回值的实例
Feb 21 #Javascript
You might like
全国FM电台频率大全 - 4 山西省
2020/03/11 无线电
在PHP里得到前天和昨天的日期的代码
2007/08/16 PHP
php内存缓存实现方法
2015/01/24 PHP
PHP异常类及异常处理操作实例详解
2018/12/19 PHP
启用OPCache提高PHP程序性能的方法
2019/03/21 PHP
在JavaScript中使用inline函数的问题
2007/03/08 Javascript
javascript十个最常用的自定义函数(中文版)
2009/09/07 Javascript
js数字输入框(包括最大值最小值限制和四舍五入)
2009/11/24 Javascript
jquery(live)中File input的change方法只起一次作用的解决办法
2011/10/21 Javascript
javascript游戏开发之《三国志曹操传》零部件开发(四)用地图块拼成大地图
2013/01/23 Javascript
js时间日期和毫秒的相互转换
2013/02/22 Javascript
js中同步与异步处理的方法和区别总结
2013/12/25 Javascript
为指定的元素添加遮罩层的示例代码
2014/01/15 Javascript
一行命令搞定node.js 版本升级
2014/07/20 Javascript
js实现顶部可折叠的菜单工具栏效果实例
2015/05/09 Javascript
javascript实现仿百度图片的瀑布流加载效果
2016/04/20 Javascript
vueJS简单的点击显示与隐藏的效果【实现代码】
2016/05/03 Javascript
使用jquery+iframe做一个ajax上传效果(实例)
2017/08/24 jQuery
基于模板引擎Jade的应用(详解)
2017/12/12 Javascript
javascript数组拍平方法总结
2018/01/20 Javascript
Vue.js实现tab切换效果
2019/07/24 Javascript
vscode 配置vue+vetur+eslint+prettier自动格式化功能
2020/03/23 Javascript
NodeJS开发人员常见五个错误理解
2020/10/14 NodeJs
[01:14]辉夜杯战队访谈宣传片—NEWBEE.Y
2015/12/26 DOTA
利用python修改json文件的value方法
2018/12/31 Python
通过实例了解python__slots__使用方法
2020/09/14 Python
VSCode 自定义html5模板的实现
2019/12/05 HTML / CSS
Myprotein瑞典官方网站:畅销欧洲英国运动营养品牌
2018/01/22 全球购物
大型营销活动计划书
2014/04/28 职场文书
新文化运动的基本口号
2014/06/21 职场文书
2014年中秋节活动总结
2014/08/29 职场文书
成本会计实训报告
2014/11/05 职场文书
2015年税务稽查工作总结
2015/05/26 职场文书
班主任寄语2016
2015/12/04 职场文书
浅析MongoDB之安全认证
2021/06/26 MongoDB
JavaScript与JQuery框架基础入门教程
2021/07/15 Javascript