首頁  >  文章  >  web前端  >  從0到1學習node(二)建造http伺服器

從0到1學習node(二)建造http伺服器

大家讲道理
大家讲道理原創
2017-01-24 15:40:401126瀏覽

在上節的課前學習中,我們了解了一下不同模組規範之間的聯繫與區別。本節我們正式開始node的學習,首先我們先從建立一個http伺服器,能運行簡單的程式開始說起。

1. hello world

最經典的hello world。首先我們建立一個server.js來保存我們的程式碼:

console.log( 'hello world' );

在終端輸入node server.js運行:

node server.js

終端就會輸出 的字樣。可是我們一個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 2. form表單

剛才我們只是在頁面中輸出了一段簡單的文本,現在我們要在頁面中呈現一個表單,可以讓用戶輸入資訊並進行提交:

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

http.createServer(function(request, response){
var html = &#39;<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>&#39;;

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

修改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(&#39;http&#39;),
url = require(&#39;url&#39;);

http.createServer(function(request, response){
    var html = &#39;<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>&#39;;
    
    var query = url.parse( request.url, true ).query;
    if( query.submit ){
        var data = &#39;<p><a href="/">back</a></p>&#39;+
            &#39;<p>username:&#39;+query.username+&#39;</p>&#39;+
            &#39;<p>password:&#39;+query.password+&#39;</p>&#39;+
            &#39;<p>age:&#39;+query.age+&#39;</p>&#39;;
         
        response.writeHead(200, {&#39;content-type&#39;: &#39;text/html&#39;});
        response.write(data);
    }else{
        response.writeHead(200, {&#39;content-type&#39;: &#39;text/html&#39;});
        response.write(html);
    }
    response.end(); // 结束
}).listen(3000);
console.log(&#39;server has started...&#39;);

我們再次運行提交後就能在頁面中顯示出數據了。

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: &#39;?username=111113&password=123&age=122&submit=submit&#39;,    
      query: &#39;username=111113&password=123&age=122&submit=submit&#39;,   
       pathname: &#39;/&#39;,    
       path: &#39;/?username=111113&password=123&age=122&submit=submit&#39;,    
       href: &#39;/?username=111113&password=123&age=122&submit=submit&#39;
       }

如果將第2個參數設為true,則會將返回結果中的query屬性解析為一個對象,其他屬性不變;預設值為false ,即query屬性是一個字串:

url.parse(request.url, true);result:Url {
...
query: {
    username: &#39;111113&#39;,
    password: &#39;123&#39;,
    age: &#39;122&#39;,
    submit: &#39;submit&#39; },
...
}

因此我們可以通過如下語句判斷是否有提交數據並獲取提交數據,然後再輸出到中即可:

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

2.2 獲取表單POST方式提交的資料

現在我們使用post方式來提交資料。因為POST請求一般都比較「重」 (使用者可能會輸入大量的內容),如果用阻塞的方式來處理處理,必然會導致使用者操作的阻塞。因此node將post資料拆分為許多小的資料塊,然後透過data事件(表示新的小資料塊到達了)和end事件傳遞這些小資料塊(表示所有的資料都已經接收完畢)。 所以,我們的思路應該是:在data事件中取得資料塊,在end事件中操作資料。

// server.js
var http = require(&#39;http&#39;),
querystring = require(&#39;querystring&#39;);

http.createServer(function(request, response){
    var html = &#39;<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>&#39;;
    
    if( request.method.toLowerCase()==&#39;post&#39; ){
        var postData = &#39;&#39;;

        request.addListener(&#39;data&#39;, function(chunk){
            postData += chunk;
        });

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

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

這段程式碼與上段程式碼項目,主要有的幾個變化是:

    不再引入url模組, 改用引入querystring模組。因為我們不再對URL進行操作了,也沒必要引入了;
  1. 使用
  2. request.method.toLowerCase()=='post'

    判斷當前是否有資料提交;

    data
  3. 在data事件中進行資料的拼接,在end事件中進行的處理;
  4. response.end()

    寫在了end事件內部,因為end事件是非同步操作,因此必須得資料輸出完成之後才能執行response .end()

  5. 我們在控制台中可以看出,postData是這樣的一個字串:

&#39;username=123&password=123&age=23&submit=submit&#39;;

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

3. 路由

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

页面初始化:

// starter.js  页面初始化

function start(request, response){
    var html = &#39;<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>&#39;;
    
    response.writeHead(200, {"Content-Type":"text/html"});
    response.write( html );
    response.end();
}
exports.start = start;

展示获取的数据:

// uploader.js 展示获取的数据var querystring = require(&#39;querystring&#39;);function upload(request, response){    var postData = &#39;&#39;;

    request.addListener(&#39;data&#39;, function(chunk){
      postData += chunk;
    });
    
    request.addListener(&#39;end&#39;, function(){        var data = querystring.parse(postData);        console.log( &#39;postData: &#39;+postData );        console.log(data);        var s = &#39;<p><a href="/">back</a></p>&#39;+            &#39;<p>username:&#39;+data.username+&#39;</p>&#39;+            &#39;<p>password:&#39;+data.password+&#39;</p>&#39;+            &#39;<p>age:&#39;+data.age+&#39;</p>&#39;;

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

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

// server.jsvar http = require(&#39;http&#39;),
url = require(&#39;url&#39;);

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

我们任意改变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.jsvar http = require(&#39;http&#39;),
url = require(&#39;url&#39;),
starter = require(&#39;./starter&#39;),
uploader = require(&#39;./uploader&#39;);

http.createServer(function(request, response){    var pathname = url.parse(request.url).pathname;    var routeurl = {        &#39;/&#39; : starter.start,        &#39;/show&#39; : uploader.upload
    }    if( typeof routeurl[pathname]=== &#39;function&#39; ){
        routeurl[pathname](request, response);
    }else{        console.log(&#39;404 not found!&#39;);
        response.end();
    }
}).listen(3000);console.log(&#39;server has started...&#39;);

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

4. 图片上传并显示

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

npm install formidable --save-dev

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

// starter.js
function start(request, response){
    var html = &#39;<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>&#39;;
    response.writeHead(200, {"Content-Type":"text/html"});
    response.write( html );
    response.end();
}
exports.start = start;

4.1 图片上传

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

// uploader.jsvar formidable = require(&#39;formidable&#39;),
util = require(&#39;util&#39;),
fs = require(&#39;fs&#39;);function upload(request, response){    if( request.method.toLowerCase()==&#39;post&#39; ){        var form = new formidable.IncomingForm();

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

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

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

received upload:

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

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

4.2 图片显示

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

// shower.jsvar fs = require(&#39;fs&#39;),
url = require(&#39;url&#39;);function show(request, response){    var query = url.parse(request.url, true).query,
        imgurl = query.src;    // 读取图片并进行输出
    // 这里读取链接中的src参数,指定读取哪张图片  /show?src=1484234660592.png
    fs.readFile(&#39;./img/&#39;+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 = {  
  &#39;/&#39; : starter.start,   
   &#39;/upload&#39; : uploader.upload,  
   &#39;/show&#39; : shower.show 
   // 添加
   };


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

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

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

5. 综合

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

6. 接口的实现

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

创建一个 inter.js :

// inter.jsvar url = require(&#39;url&#39;);function init(request, response){    if( request.method.toLowerCase()==&#39;get&#39; ){        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 + &#39;(&#39; + JSON.stringify(data) + &#39;)&#39; );
         }else{            // json
            response.end( JSON.stringify(data) );
        }
    }
}
exports.init = init;

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

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

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

7. 总结

这节还是写了不少的内容,最核心的就是讲解如何搭建一个简单的http服务器,进行数据和图片的提交与处理,在最后稍微讲了下接口的编写,后面有机会的话,会再具体讲解下接口的编写。


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn