Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tokunami/90546413e27c8ee8b55b to your computer and use it in GitHub Desktop.
Save tokunami/90546413e27c8ee8b55b to your computer and use it in GitHub Desktop.

Node.js + Express + socket.io で、入力に応じて画像を返す

  • Node.js v0.11.11
  • Express v3.5.2
  • socket.io v0.9.16
事前準備
  • 画像を数枚準備 (今回は neko.jpg, inu.jpg, umi.jpeg, hana.jpg, fukuoka.jpg)
Expressでひな形を作成し、パッケージとsocket.ioのインストール (ディレクトリ名 chat_gazou)
$ sudo npm install express@3
$ express chat_gazou
$ cd chat_gazou && npm install
$ npm install socket.io

なお、今回はnpm installnpm install socket.ioを別個に実施したが、後から調べたところ、expressでchat_gazouを作成後、package.jsonにsocke.ioの項を書き加えてからnpm installすると、socket.ioを別個にインストールする必要がない模様。

{
  "name": "application-name",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.5.2",
    "jade": "*"
    "socket.io": "*"
  }
}
サーバーサイド sever.jsの書き換え
  • デフォルトでは app.js だが、今回はわかりやすくするためにserver.jsにファイル名を変更した。
  • 変数の宣言を書き加える (これと書いてある行)
/**
 * Module dependencies.
 */

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');

var io = require('socket.io');  //これ socket.io インスタンスの作成
var fs = require('fs');   //これ ファイルシステムモジュールfsのインスタンス作成

var app = express();
var server = http.createServer(app);  //これ
var io = io.listen(server); //これ
  • //all environmentsは変更しない

  • app.get('/', routes.index);を書き換え

//ローカルを踏んだ時にindex.jadeをレンダリングする (titleは ChatRoooom)

app.get('/', function(req,res){
  res.render('index.jade',{
    title: 'ChatRoooom'
  });
});

//localhost:3000/nekoにアクセスした時に neko.jpg を読み込んで送る
app.get('/neko', function(req, res){
    fs.readFile('neko.jpg',function(err, data){   //neko.jpgを読み込み、function(err,data)の呼び出し
      res.set('Content-Type', 'image/jpeg');  //ヘッダの指定 jpeg
      res.send(data);   //送信
    });
});

//以下、nekoと同じ要領でinu、umi、hana、fukuokaを作る
  • app.getの下に出てくるhttp.createServer(app)は、すでにserverとして宣言しているので、書き換える
server.listen(app.get('port'), function(){
  • 最後にsocketに関する部分を書き加える
io.sockets.on('connection', function(socket) {    //サーバがクライアントとの接続を確立すると、サーバで 'connection'イベントが発生
  socket.on('msg post', function(msg) {   //サーバで 'msg post' イベントが発生し、コールバックが実行される
    console.log(msg);
    io.sockets.emit('msg push', msg);   //サーバが 'msg push' イベントを発火して受信したメッセージを送ってきた本人とそれ以外のクライアントに送信
  });

});
クライアントサイド client.js の作成
  • 場所 chat_gazou > public > javascripts > client.js
$(function(){
  var socket = io.connect('http://localhost/');   //クライアントが 'http://localhost' にソケット接続を要求

  socket.on('connect',function(){   //サーバがクライアントとの接続を確立すると、クライアントで 'connect' イベントが発生
    console.log('connected.');
  });

  $('#comment_form').on('submit',function(){
    var msg = $('#comment').val();
    socket.emit('msg post', msg);   //クライアントが 'msg post' イベントを発火して文字列 'msg' を送信
    $('#comment').val('');    //コメント欄の初期化
  });

  socket.on('msg push', function(msg){    //クライアントで 'msg push' イベントが発生し、コールバックが実行される
    $('#img').attr("src", msg);   //img srcの書き換え
    $('#imgtitle').html(msg);     //画像タイトルの書き換え
  });

});
  • $があたまについているのはjQuery
  • 全体を $(function(){}) でくるむのは、htmlファイル内に記述されたDOM要素をすべて読み込んでから引数のfunctionを実行するようにするためのお作法。$(document).ready()の省略形。
index.jadeの書き換え
doctype html
html
  head
    title= title
    link(rel='stylesheet', href='/stylesheets/style.css')
    script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js')
    script(type='text/javascript', src='/socket.io/socket.io.js')
    script(type='text/javascript', src='/javascripts/client.js')
  body
    h1= title
    form(action="javascript:void(0);" id="comment_form")
      label comment:
        input(type="text" id="comment")
        input(type="submit" value="送信")
    hr

    image(src='http://localhost:3000/umi' , id="img")   //初期画面ではumi.jpgを表示
    p(id="imgtitle")
    hr
    
  • index.jadeで指定したid imgimgtitle をクライアント側で書き換えている(msg push イベントが発生して実行されるコールバック関数)。
実行
$ node server
  • localhost:3000で動作確認。
積み残しなど
  • listen関数について調べる。(var io が2回出てきていることに関して、2回めのio.listen(server)は結局何なのか)

  • 事前に用意した画像の名前以外が打ち込まれた時の対処。

  • 画像が表示されるのではなくダウンロードされる事態が発生し、てこずった。res.send()のところでヘッダ指定がうまくいっていなかったようで、公式HP記載のres.set()で事前にヘッダを指定でき、解決した。ほかの諸問題も公式HPで解決できることが多かった。

/**
* Module dependencies.
*/
var express = require('express')
, routes = require('./routes')
, user = require('./routes/user')
, http = require('http')
, path = require('path')
, io = require('socket.io');
var fs = require('fs');
var app = express();
var server = http.createServer(app);
var io = io.listen(server);
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', function(req,res){
res.render('index.jade',{
title: 'ChatRoooom'
});
});
app.get('/neko', function(req, res){
fs.readFile('neko.jpg',function(err, data){
res.set('Content-Type', 'image/jpeg');
res.send(data);
});
});
app.get('/umi', function(req, res){
fs.readFile('umi.jpg',function(err, data){
res.set('Content-Type', 'image/jpeg');
res.send(data);
});
});
app.get('/hana', function(req, res){
fs.readFile('hana.jpg',function(err, data){
res.set('Content-Type', 'image/jpeg');
res.send(data);
});
});
app.get('/inu', function(req, res){
fs.readFile('inu.jpg',function(err, data){
res.set('Content-Type', 'image/jpeg');
res.send(data);
});
});
app.get('/fukuoka', function(req, res){
fs.readFile('fukuoka.jpg',function(err, data){
res.set('Content-Type', 'image/jpeg');
res.send(data);
});
});
server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
io.sockets.on('connection', function(socket) {
socket.on('msg post', function(msg) {
console.log(msg);
io.sockets.emit('msg push', msg);
});
});
@tokunami
Copy link
Author

共通のコールバック関数

下記コードに書き換えて、実行を確認しました。

app.get('/:msg', function(req, res){
    fs.readFile('./public/images/'+req.params.msg+'.jpg', function(err, data){
        res.set('Content-Type', 'image/jpeg');
        res.send(data);
    });
});
  • コロン (:) で始まる文字列は Path Parameter として解析される。
  • (上記コードには出てきていないが)URL を括弧で囲んだ場合、その部分は省略可能なパラメータとして解析される。
  • req.params.msg により、URLでルーティング
  • リファクタリング (refactoring) とはコンピュータプログラミングにおいて、プログラムの外部から見た動作を変えずにソースコードの内部構造を整理すること。

@tokunami
Copy link
Author

エラーハンドリング
  • 用意した画像以外の名前が打ち込まれたときに、レスポンスとしてnoimage.jpgを返す。
  • fs.readFile(filename, [encoding], [callback])のコールバックは 2 つの引数が渡される (err, data)ので、エラー処理を書いた。
app.get('/:msg', function(req, res){
    fs.readFile('./public/images/'+req.params.msg+'.jpg', function(err, data){

      if(err){
        console.log('image was not read');

        fs.readFile('./public/images/noimage.jpg',function(err,data){
          res.set('Content-Type', 'image/jpeg');
          res.send(data);
        });

      }else{
        console.log('image was read');
        res.set('Content-Type', 'image/jpeg');
        res.send(data);
      }

    });
});

もっといいコードがありそう。。。

  • また、console logを出してみたところ、リクエスト1回に対して2回console logが出てくる(そしてGETも2回でてくる)。なお、GETは初期バージョンでは1回しか出てこず、どこから出るようになったのか不明。なぜ。理由調査する。…と思ったら、Ctrl+Cで中断して再度実行したら直りました。

@stoshiya
Copy link

エラー処理も踏まえて私ならこのように書きます.ご参考までに.

var IMAGE_DIR = '...';

app.get('/:msg', function(req, res) {
  // まず引数に相当する部分としてmsgをチェック.型や範囲など.
  if (typeof req.params.msg === 'undefined') {
    res.send(400);
    return;
  }

  fs.readFile(path.join(IMAGE_DIR , req.params.msg + '.jpg'), function(err, data) {
    if (err) {
      console.error(err);
      req.send(500); // エラーの内容にもよるがとりあえず
      return;
    }
    res.set('Content-Type', 'image/jpeg');
    res.send(data);
  });
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment