情報アイランド

「情報を制する者は世界を制す」をモットーに様々な情報を提供することを目指すブログです。現在はプログラミング関連情報が多めですが、投資関連情報も取り扱っていきたいです。

expressモジュールによるHTTPサーバ(21)範囲リクエスト

この記事は「expressモジュールによるHTTPサーバ」という連載記事の21つ目の記事です。

その他の記事に関しては下を参照してください。

また、HTTP自体について知りたい方は下を参照してください。

この記事ではexpressモジュールを使って範囲リクエストを処理するHTTPサーバを作成する方法について説明します。

範囲リクエスト

範囲リクエストを処理するにはreq.range関数を使用します。

var express = require('express');

var app = express();
app.use(function (req, res, next) {
    var ranges = req.range(10000, {
        combine: true
    });
    next();
});

第1引数に範囲の最大値を指定します。

第2引数にオプションを指定します。この引数は指定しなくても構いません。

オプションには下のようなものがあります。

  • combine・・・範囲リクエストの範囲のリストの複数の範囲が重複したり、隣接したりしている場合に範囲を1つに纏めるかを真偽値として指定します。デフォルトはfalseです。

返り値として範囲リクエストの範囲のリストがオブジェクトの配列として得られます。そして、このオブジェクトの配列を使用して範囲リクエストの処理を行うようにします。

このオブジェクトは下のようなプロパティを有します。

  • start・・・範囲の開始インデックスを表します。
  • end・・・範囲の終了インデックスを表します。

ただし、範囲リクエストが正しい形式ではない場合には-2が返り、範囲リクエストが処理できない場合には-1が返ります。

また、HTTPリクエストが範囲リクエストではない場合にはundefinedが返ります。

範囲リクエストが処理できる場合にはHTTPレスポンスのステータスコードを206Partial Content)とし、処理できない場合には416Range Not Satisfiable)とします。

また、範囲リクエストが処理できる場合で範囲が単一である場合にはHTTPレスポンスボディには単一の範囲のデータをそのまま格納し、Content-TypeヘッダフィールドやContent-Rangeヘッダフィールドなどを設定します。

範囲が複数である場合にはHTTPレスポンスボディには複数の範囲のデータをmultipart/byterangesメディアタイプ(MIMEタイプ)形式で格納し、Content-Typeヘッダフィールドなどを設定します。なお、multipart/byterangesメディアタイプ形式は一般的なmultipartトップレベルメディアタイプ形式と同じです。

また、範囲リクエストが処理できない場合にはContent-Rangeヘッダフィールドなどを設定します。

サンプルコード1

範囲リクエストに対応し、クライアントからリクエストを受け取ったらテキストデータを返すHTTPサーバを作成し、起動します。

Enterキーが押されたらサーバを停止し、プログラムを終了します。

express-server-range.js

var express = require('express');
var http = require('http');

var app = express();
app.set('env', 'development');
app.set('x-powered-by', false);
app.set('case sensitive routing', true);
app.set('strict routing', true);
app.get('/', function (req, res, next) {
    var data = 'welcome to pizyumi\'s website. this is home page.\r\n';
    var ranges = req.range(data.length, {
        combine: true
    });
    if (ranges === -2) {
        res.status(400);
        res.type('text/plain; charset=utf-8');
        res.send(http.STATUS_CODES[400] + '\r\n');
    }
    else if (ranges === -1) {
        res.status(416);
        res.type('text/plain; charset=utf-8');
        res.set('Content-Range', 'bytes */' + data.length);
        res.send(http.STATUS_CODES[416] + '\r\n');
    }
    else if (ranges === undefined) {
        res.status(200);
        res.type('text/plain; charset=utf-8');
        res.send(data);
    }
    else if (ranges.length === 1) {
        res.status(206);
        res.type('text/plain; charset=utf-8');
        res.set('Content-Range', 'bytes ' + ranges[0].start + '-' + ranges[0].end + '/' + data.length);
        res.send(data.substring(ranges[0].start, ranges[0].end + 1));
    }
    else {
        var boundary = 'xxx'
        var send = '';
        for (var i = 0; i < ranges.length; i++) {
            send += '--' + boundary + '\r\n';
            send += 'Content-Type: text/plain; charset=utf-8\r\n';
            send += 'Content-Range: bytes ' + ranges[i].start + '-' + ranges[i].end + '/' + data.length + '\r\n';
            send += '\r\n';
            send += data.substring(ranges[i].start, ranges[i].end + 1) + '\r\n';
        }
        send += '--' + boundary + '--';
        res.status(206);
        res.type('multipart/byteranges; boundary=' + boundary);
        res.send(send);
    }
});
app.get('*', function (req, res, next) {
    res.status(404);
    res.send(http.STATUS_CODES[404] + '\r\n');
});
app.post('*', function (req, res, next) {
    res.send('request method is post.\r\n');
});
app.all('*', function (req, res, next) {
    res.status(501);
    res.send(http.STATUS_CODES[501] + '\r\n');
});
var server = app.listen(3000, function () {
    console.log('http server is running...press enter key to exit.');

    process.stdin.on('data', function (data) {
        if (data.indexOf('\n') !== -1) {
            server.close(function () {
                console.log('http server closed.');
                process.exit(0);
            });
        }
    });
});
server.on('error', function (err) {
    console.error(err);
    process.exit(1);
});

使用パッケージ

  • express
    npm install expressでインストールします。

実行結果

実行結果の確認のためにcURLを使用しています。

コードを実行するとHTTPサーバが起動します。

C:\work\node>node express-server-range.js
http server is running...press enter key to exit.

cURLを使ってサーバに対して普通のHTTPリクエストを行います。

C:\work\node>curl -v http://localhost:3000/
*   Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.46.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=utf-8
< Content-Length: 50
< ETag: W/"32-juTlohfTcZy04HpD+lfkLw"
< Date: Thu, 16 Mar 2017 14:44:32 GMT
< Connection: keep-alive
<
welcome to pizyumi's website. this is home page.
* Connection #0 to host localhost left intact

サーバから正しくレスポンスが返っていることが分かります。

cURLを使ってサーバに対して範囲リクエストを行います。

C:\work\node>curl -H Range:bytes=0-9 -v http://localhost:3000/
*   Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.46.0
> Accept: */*
> Range:bytes=0-9
>
< HTTP/1.1 206 Partial Content
< Content-Type: text/plain; charset=utf-8
< Content-Range: bytes 0-9/50
< Content-Length: 10
< ETag: W/"a-u/McgAoMzbO7/JJ86SpSgg"
< Date: Thu, 16 Mar 2017 14:45:17 GMT
< Connection: keep-alive
<
welcome to* Connection #0 to host localhost left intact
C:\work\node>curl -H Range:bytes=0-9,20-29 -v http://localhost:3000/
*   Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.46.0
> Accept: */*
> Range:bytes=0-9,20-29
>
< HTTP/1.1 206 Partial Content
< Content-Type: multipart/byteranges; boundary=xxx; charset=utf-8
< Content-Length: 191
< ETag: W/"bf-pnoNKe5FyAVaPuKT1jLLmQ"
< Date: Thu, 16 Mar 2017 14:45:40 GMT
< Connection: keep-alive
<
--xxx
Content-Type: text/plain; charset=utf-8
Content-Range: bytes 0-9/50

welcome to
--xxx
Content-Type: text/plain; charset=utf-8
Content-Range: bytes 20-29/50

 website.
--xxx--* Connection #0 to host localhost left intact
C:\work\node>curl -H Range:bytes=0-9,20-29,40-49 -v http://localhost:3000/
*   Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.46.0
> Accept: */*
> Range:bytes=0-9,20-29,40-49
>
< HTTP/1.1 206 Partial Content
< Content-Type: multipart/byteranges; boundary=xxx; charset=utf-8
< Content-Length: 284
< ETag: W/"11c-xY9Otbc6zBmvOXHL3gMqqw"
< Date: Thu, 16 Mar 2017 14:46:08 GMT
< Connection: keep-alive
<
--xxx
Content-Type: text/plain; charset=utf-8
Content-Range: bytes 0-9/50

welcome to
--xxx
Content-Type: text/plain; charset=utf-8
Content-Range: bytes 20-29/50

 website.
--xxx
Content-Type: text/plain; charset=utf-8
Content-Range: bytes 40-49/50

me page.

--xxx--* Connection #0 to host localhost left intact

サーバから正しくレスポンスが返っている(ステータスコードが206Partial Content)のレスポンスが返っている)ことが分かります。

Enterキーを押すとサーバが停止し、プログラムが終了します。

関連

pizyumi
プログラミング歴19年のベテランプログラマー。業務システム全般何でも作れます。現在はWeb系の技術を勉強中。
スポンサーリンク

-express, Node.js