これまでに サーバ側コードとクライアント側コードを、個別に学んできた。
これからその連携動作も体験していきましょう。
方針:なるべくライブラリに頼らない
(ネイティブのJSの機能が充実してきたことを受けて)使わないで進めます。
ファイルをドラッグ(あんど ドロップ)する場所を作る(pugで記述してある)
doctype html
div#up
style.
#up
width: 20vw; height: 20vh;
background: blue;
}
script(src="f/upld0.js")
今回、スクリプトは別ファイルにした。(pugファイルの中に展開させると編集の作業が面倒 + JSのコードを書き換えたときにも pugコマンドでの変換が必要になる、という2つの理由)。
例えば Atomエディタで編集するなら pug 用のプラグインを入れると少し快適になる。
id名 ‘up’(名前は何でもいいが)の div要素、前回は position:abusolute
を指定したが今回は動かさないのでこれは不要。
pugで書いたものをHTMLに変換するためには、(上記を up.pug に保存したとして)、(ファイルを保存したフォルダからコマンドプロンプトを開いて)下記のようなコマンドで変換。
pug up.pug -P
ドラッグを受け付けるコード
const up=document.getElementById('up')
function paint(col){up.style.backgroundColor=col}
up.addEventListener('dragover',ev=>{
ev.preventDefault();
paint('yellow')
})
up.addEventListener('dragleave',ev=>paint('blue'))
up.addEventListener('drop',ev=>{
ev.preventDefault(); paint('red');
// ... ここをこのあと充実させる
})
落とされたファイルを認識し、関数 sendit(後で書く) に渡す
var f=ev.dataTransfer.files[0];
var fileReader = new FileReader();
fileReader.onload=ev=>sendit(event.target.result,f.name)
fileReader.readAsBinaryString(f)
paint('green')
送信
function sendit(content,fname){
// console.log(content)
fetch(`http://localhost:58080/up0/${fname}`,{
method: 'POST', body: content,
headers: {'content-type': 'application/octet-stream'}
})
.then(res=>{
paint('blue')
}).catch(err=>{
console.log(err); paint('blue')
})
}
ページ遷移を行わずに、サーバとの間でデータ通信を行う。つまりこの処理は一種の Ajax と言える。
Ajaxは 最近までは XMLHttpRequest オブジェクト(JavaScriptネイティブ)や、jQueryの Ajax メソッドを使うことが多かった
XMLHttpRequest の使い方は、先に述べた fileReader と同じ、3段階(生成、イベント処理、呼出)の書き方(かなりまだるっこしい)。
が、最近若干の変化があった。
上記は、Fetch API を使う例(すでに解説した Promise の使い方の1つの例にもなっている)
アップロードの際は、(HTTPの)メソッドとして POST を使う。
fetch の第1引数は、呼び出したい(サーバの)URL、第2引数がオプション(オブジェクトの形で)。
content-type
の中のハイフン ( - ) (だと我々が思っているもの)は、 「マイナス」でもある。JSではこれを裸で使うと、引き算の式だと思われてしまうので、 ここはクォートする(明示的に文字列にする)必要がある。
up.style.backgroundColor=col
という式では、 CSSの属性名(を、多くの場合はそのままJSでの属性名として流用しているが)background-color
で、ハイフンを取り除いて代わりにキャメルケースにしたもの、を使っていることも確認しておいて下さい。これでクライアント側の準備は整ったが、サーバが動作していないと、実際に通信を試みた時点でエラーを発生させるだろう。
次に進んでみよう。
node.js で動作させることになるだろう。
サーバの基本動作としては、定番となっているフレームワーク/ライブラリである express.js を使うことにする。
// 先頭部
const express = require('express')
const app = express()
// 末尾
var server = app.listen(58080,()=>{
console.log("listening at port %s", server.address().port);
});
上記の(2つに分けて提示した)コードを1つの .js ファイルに書き込み(ここでは up.js としておく)、 コマンドプロンプトを(このファイルを保存したフォルダから)起動して、以下のように起動するとサーバが動作する。
node up.js
この段階では何もしないサーバなので、中身をこれから作り込む。次節以降のコードは、上記の2つのコード片の間に挿入していくことになる。
この先のコードは前節で設定した app オブジェクトに関するメソッド呼出を中止に進める。
app.post('/up0/:fname', (req,res)=>{
// console.log(req.params.fname, req.body)
// この中身は次節で
res.end("uploaded.");
});
受け付けるHTTPメソッドによって、(主に使われるものとしては)app.get と app.post を使い分ける。
一般的には get の方が広く使われているが、この課程ではアップロードなのでまず post から紹介する(でも使い方はほぼ同じ)。
第1引数(文字列)は、HTTPリクエストで要求される資源(ディレクトリ名とファイル名)のパターン。このパターンの要求を(このメソッドで)受け付けた時に、第2引数のコールバック(無名関数)が呼び出される、というルール(対応関係)をこうやって示していく (Webサーバ用語では「ルーティング」と呼ぶ)。
コールバックは引数を2つ受け取るという約束になっている。1つめが要求(Request) を表象するオブジェクト、2つめは応答(Response)を表象する。
{fname: '....'}
のような値になっている筈( …. の部分がファイル名)。ここで、先にクライアント側の fetch() に第一引数として渡した URL(の、前半部分の localhost を表すドメイン名までを取り除いた、要求する資源に相当する部分)、
/up0/${fname}
を振り返って頂きたい。
スラッシュで分けられた最初のパートが、ディレクトリ名 up0
としてあった。 第2パートに、アップロードするファイル名を置いた。これを受け取るパターンが、 (expressでの表現方法で)‘/up0/:fname’ となる。
パターンの中で、コロンが前置された部分 :fname
は、その文字列そのものではなく、変数扱いになり、2つめのスラッシュの右(送信した側でファイル名を置いた箇所) に置かれた文字列が、この app.post の(コールバックの)中では、
req.params.fname
として設定されている。
受け取ったデータの保存(次節)が終了したら、(サーバ側コードの責任として) クライアントに適切なレスポンスを返す必要がある。
res.render
を使ってWebページ(HTML)の形で返すことが多いが、res.send
で送ってお茶を濁しておく。HTTPのPOSTリクエストのBODY部として送られてきた、リクエスト本体は、 req.body
で取り出すことができる。
req.params.fname
にセットされた)ファイル名があれば、 サーバ側にファイルを保存することができる。ただし、POSTリクエストのBODY部には、様々な種類のデータが(場合によってはそれに応じたエンコーディング等を行って)送られてくるため、それに応じた受け取り方を準備する必要がある。
content-type: application/octet-stream
として、つまり生データとして(加工なしで)BODYに置かれている。これを扱うために以下のようなコードを挟んでおくこと。// 先頭近辺で
const bodyParser = require('body-parser')
// ...
// 前節の app.post の前に
app.use(bodyParser.raw({type: 'application/octet-stream', limit : '2mb'}))
// limit の値は適当に設定してある
なお、bodyParserは、以前は express に添付されていたが今は独立したモジュール(ライブラリ)として提供されているので、 これを使うためには事前に、
npm install body-paraser
しておく必要がある。
body-paraser
をrequireしたオブジェクトを保持する変数名として、body-parser
をそのまま使えない(理由は前に述べた)ので、 こういうケースでは bodyParser を使うことになる。ファイルの保存(ディスクへの書き込み)は、にfs モジュールを使う。app.post の中核部(前節で保留した箇所)は以下のような処理になるだろう。
// 先頭部(が適切だが、fs を使う直前でも差支えはない)
const fs = require('fs')
// ...
// app.post の中
fs.writeFile(`./dat/${req.params.fname}`,req.body,(err)=>{
if(err) console.log(err);
else console.log('write end');
})
dat
としてある。起動する前にそのディレクトリは用意しておくこと。本ページの冒頭で作ったアップローダのWebページ(pug または html)、 当初は独立したページとして(エクスプローラから開く、といった使い方を前提に) 作っていた、が、
このページもサーバから提供するようにしよう。
htmlファイルを準備してもいいが、せっかくpugがあるので、expressの view engine として pug を使い、そのエンジンでhtml化したものをブラウザに送る形にしよう。
まず、(サーバを起動するディレクトリを起点として)ディレクトリ views を作り (Webアプリケーションフレームワークでは定番の場所、express ではデフォルトの置き場所)、 先に作った pug ファイルを、そこに移す(コピーでもいい)。 ファイル名は views/up.pug
となる、と仮定する。
// 下記の app.get よりも前(かつ const app は設定された後)に
app.set('view engine','pug')
// app.post の直前ぐらいに入れる
app.get('/up',(req,res)=>res.render('up'))
このサービスを通じて提供される入口ページを表示させるためには、(サーバが起動している状態で)
http://localhost:58080/up
にアクセスすることになる(やってみて下さい)。
静的ファイルの提供:
クライアント側JavaScriptコードを pugファイル(htmlファイル)の中にべた書きするのでなく外部 .js ファイルに出してまとめる場合、pugファイルには、
script(src="/f/upld0.js")
のように記述する。
当該JavaScriptファイルは、ここでは upld0.js という名前で、 サブディレクトリ(名前は files としておく)に置かれていることを前提とする。
このファイルを、要求があった時にそのまま(静的ファイルとして、つまり元来のWebで使われてきている流儀で)ブラウザに提供させるために、 下記のようなオマジナイが必要になる。
app.use('/f',express.static('files'))
/f/xxxxx
への要求があった時には、そこに置かれたファイルをコンテンツとして返す、というルールを宣言することになる。files.upld0.js
を返す。/up
という場所(ドキュメントルートの直下、と考えられる)なので、