Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Bluemixのボイラープレート、「Node.js Cloudant DB Web Starter」が自動生成したアプリを見ながら、Node.jsを学ぼうのコーナー。

直近、何ができるようになればいいかというと、URLにアクセスしたらJSONを返すWebサービスのひな形をどう作ったら良いのか学ぶこと。画面は要らないので仕組みを調べつつそぎ落としていくことになるのかな。

まずは、app.jsを読んでみる。

001: /**
002:  * Module dependencies.
003:  */
004: 
005: var express = require('express'), routes = require('./routes'), user = require('./routes/user'), http = require('http'), path = require('path'), fs = require('fs');
006: 
007: var app = express();
008: 
009: var db;
010: 
011: var cloudant;
012: 
013: var fileToUpload;
014: 
015: var dbCredentials = {
016:    dbName : 'my_sample_db'
017: };
018: 
019: var bodyParser = require('body-parser');
020: var methodOverride = require('method-override');
021: var logger = require('morgan');
022: var errorHandler = require('errorhandler');
023: var multipart = require('connect-multiparty')
024: var multipartMiddleware = multipart();

ここはモジュールを読み込んでいるところ。requireすると何が戻るのかよくわかっていないけど。

026: // all environments
027: app.set('port', process.env.PORT || 3000);
028: app.set('views', __dirname + '/views');
029: app.set('view engine', 'ejs');
030: app.engine('html', require('ejs').renderFile);
031: app.use(logger('dev'));
032: app.use(bodyParser.urlencoded({ extended: true }));
033: app.use(bodyParser.json());
034: app.use(methodOverride());
035: app.use(express.static(path.join(__dirname, 'public')));
036: app.use('/style', express.static(path.join(__dirname, '/views/style')));

appはExpressで、Expressというのはnode.jsのWebフレームワークのこと。

ポートの指定、テンプレートエンジンの指定、ロガーの指定、静的コンテンツの場所などを指定している。今回の目的にはExpressはちょっと守備範囲が広すぎるような感じがするので、使わないか、違うものに差し替えることを目指すけど、その場合にここの設定内容はどこへ指定すれば良いのかを調べなきゃいけない。たぶんもう少し低レベルのHTTPサーバ部分があるはず。

038: // development only
039: if ('development' == app.get('env')) {
040:    app.use(errorHandler());
041: }

エラーハンドラーを動かすには、devモードにする必要がある。たぶん環境変数に何か指定するんだと思うんだけど、細かいことは後で調べる。

043: function initDBConnection() {
044:    
045:    if(process.env.VCAP_SERVICES) {
046:       var vcapServices = JSON.parse(process.env.VCAP_SERVICES);
047:       // Pattern match to find the first instance of a Cloudant service in
048:       // VCAP_SERVICES. If you know your service key, you can access the
049:       // service credentials directly by using the vcapServices object.
050:       for(var vcapService in vcapServices){
(中略)
068:       }
069:       if(db==null){
070:          console.warn('Could not find Cloudant credentials in VCAP_SERVICES environment variable - data will be unavailable to the UI');
071:       }
072:    } else{
073:       console.warn('VCAP_SERVICES environment variable not set - data will be unavailable to the UI');
074:       // For running this app locally you can get your Cloudant credentials 
075:       // from Bluemix (VCAP_SERVICES in "cf env" output or the Environment 
076:       // Variables section for an app in the Bluemix console dashboard).
077:       // Alternately you could point to a local database here instead of a 
078:       // Bluemix service.
(中略)
093:    }
094: }
095: 
096: initDBConnection();

DBの設定。昨日、node app.jsしたときに出ていたエラーメッセージはここにある。ローカル開発中用のDB設定はここにしろとのことである。

098: app.get('/', routes.index);

app.get(path, func)でこのパスにアクセスした時の動きを指定する。指定されているのはroutes/index.jsに記述されている関数。中身は、ただindex.htmlを表示しているだけである。

100: function createResponseData(id, name, value, attachments) {
101: 
102:    var responseData = {
103:       id : id,
104:       name : name,
105:       value : value,
106:       attachements : []
107:    };
108:    
109:     
110:    attachments.forEach (function(item, index) {
111:       var attachmentData = {
112:          content_type : item.type,
113:          key : item.key,
114:          url : '/api/favorites/attach?id=' + id + '&key=' + item.key
115:       };
116:       responseData.attachements.push(attachmentData);
117:       
118:       });
119:    return responseData;
120: }

まあ、responseつくるんだよね、たぶん。

123: var saveDocument = function(id, name, value, response) {
124:    
125:    if(id === undefined) {
126:       // Generated random id
127:       id = '';
128:    }
129:    
130:    db.insert({
131:       name : name,
132:       value : value
133:    }, id, function(err, doc) {
134:       if(err) {
135:          console.log(err);
136:          response.sendStatus(500);
137:       } else
138:          response.sendStatus(200);
139:       response.end();
140:    });
141:    
142: }

DBに値を書き込んでいる。

144: app.get('/api/favorites/attach', function(request, response) {
145:     var doc = request.query.id;
146:     var key = request.query.key;
147: 
148:     db.attachment.get(doc, key, function(err, body) {
149:         if (err) {
150:             response.status(500);
151:             response.setHeader('Content-Type', 'text/plain');
152:             response.write('Error: ' + err);
153:             response.end();
154:             return;
155:         }
156: 
157:         response.status(200);
158:         response.setHeader("Content-Disposition", 'inline; filename="' + key + '"');
159:         response.write(body);
160:         response.end();
161:         return;
162:     });
163: });


/api/favorites/attachにクエリでidとkeyを渡すと、DBに登録されているデータを返している。


165: app.post('/api/favorites/attach', multipartMiddleware, function(request, response) {
166: 
167:    console.log("Upload File Invoked..");
168:    console.log('Request: ' + JSON.stringify(request.headers));
169:    
170:    var id;
171:    
172:    db.get(request.query.id, function(err, existingdoc) {      
173:       
(中略)
181: 
182:       var name = request.query.name;
183:       var value = request.query.value;
184: 
185:       var file = request.files.file;
186:       var newPath = './public/uploads/' + file.name;      
187:       
188:       var insertAttachment = function(file, id, rev, name, value, response) {
189:          
190:          fs.readFile(file.path, function(err, data) {
191:             if (!err) {
192:                 
193:                if (file) {
194:                     
195:                   db.attachment.insert(id, file.name, data, file.type, {rev: rev}, function(err, document) {
(中略)
225:                   });
226:                }
227:             }
228:          });
229:       }
230: 
231:       if (!isExistingDoc) {
232:          existingdoc = {
233:             name : name,
234:             value : value,
235:             create_date : new Date()
236:          };
237:          
238:          // save doc
239:          db.insert({
240:             name : name,
241:             value : value
242:          }, '', function(err, doc) {
243:             if(err) {
244:                console.log(err);
245:             } else {
246:                
247:                existingdoc = doc;
248:                console.log("New doc created ..");
249:                console.log(existingdoc);
250:                insertAttachment(file, existingdoc.id, existingdoc.rev, name, value, response);
251:                
252:             }
253:          });
254:          
255:       } else {
256:          console.log('Adding attachment to existing doc.');
257:          console.log(existingdoc);
258:          insertAttachment(file, existingdoc._id, existingdoc._rev, name, value, response);
259:       }
260:       
261:    });
262: 
263: });

ファイルを保存してくれるAPI

265: app.post('/api/favorites', function(request, response) {
266: 
267:    console.log("Create Invoked..");
268:    console.log("Name: " + request.body.name);
269:    console.log("Value: " + request.body.value);
270:    
271:    // var id = request.body.id;
272:    var name = request.body.name;
273:    var value = request.body.value;
274:    
275:    saveDocument(null, name, value, response);
276: 
277: });

ちょっと使い方はわからないけど、(name, value)をDBに書き込んでいる。

279: app.delete('/api/favorites', function(request, response) {
280: 
281:    console.log("Delete Invoked..");
282:    var id = request.query.id;
283:    // var rev = request.query.rev; // Rev can be fetched from request. if
284:    // needed, send the rev from client
285:    console.log("Removing document of ID: " + id);
286:    console.log('Request Query: '+JSON.stringify(request.query));
287:    
288:    db.get(id, { revs_info: true }, function(err, doc) {
289:       if (!err) {
290:          db.destroy(doc._id, doc._rev, function (err, res) {
291:               // Handle response
292:              if(err) {
293:                 console.log(err);
294:                 response.sendStatus(500);
295:              } else {
296:                 response.sendStatus(200);
297:              }
298:          });
299:       }
300:    });
301: 
302: });

削除はDELETEメソッドを送る。

304: app.put('/api/favorites', function(request, response) {
305: 
306:    console.log("Update Invoked..");
307:    
308:    var id = request.body.id;
309:    var name = request.body.name;
310:    var value = request.body.value;
311:    
312:    console.log("ID: " + id);
313:    
314:    db.get(id, { revs_info: true }, function(err, doc) {
315:       if (!err) {
316:          console.log(doc);
317:          doc.name = name;
318:          doc.value = value;
319:          db.insert(doc, doc.id, function(err, doc) {
320:             if(err) {
321:                console.log('Error inserting data\n'+err);
322:                return 500;
323:             }
324:             return 200;
325:          });
326:       }
327:    });
328: });

変更。IDとname, valueを渡して、IDをそのname, valueで更新してる。

330: app.get('/api/favorites', function(request, response) {
331: 
332:    console.log("Get method invoked.. ")
333:    
334:    db = cloudant.use(dbCredentials.dbName);
335:    var docList = [];
336:    var i = 0;
337:    db.list(function(err, body) {
338:       if (!err) {
339:          var len = body.rows.length;
340:          console.log('total # of docs -> '+len);
341:          if(len == 0) {
342:             // push sample data
343:             // save doc
(中略)
367:          } else {
368: 
369:             body.rows.forEach(function(document) {
370:                
371:                db.get(document.id, { revs_info: true }, function(err, doc) {
372:                   if (!err) {
373:                      if(doc['_attachments']) {
374:                      
375:                         var attachments = [];
376:                         for(var attribute in doc['_attachments']){
377:                         
378:                            if(doc['_attachments'][attribute] && doc['_attachments'][attribute]['content_type']) {
379:                               attachments.push({"key": attribute, "type": doc['_attachments'][attribute]['content_type']});
380:                            }
381:                            console.log(attribute+": "+JSON.stringify(doc['_attachments'][attribute]));
382:                         }
383:                         var responseData = createResponseData(
384:                               doc._id,
385:                               doc.name,
386:                               doc.value,
387:                               attachments);
388:                      
(中略)
395:                      }   
396:                   
397:                      docList.push(responseData);
398:                      i++;
399:                      if(i >= len) {
400:                         response.write(JSON.stringify(docList));
401:                         console.log('ending response...');
402:                         response.end();
403:                      }
404:                   } else {
405:                      console.log(err);
406:                   }
407:                });
408:                
409:             });
410:          }
411:          
412:       } else {
413:          console.log(err);
414:       }
415:    });
416: 
417: });

これがGET。何もデータがなかったらサンプルデータを追加するというコードが入っている

420: http.createServer(app).listen(app.get('port'), '0.0.0.0', function() {
421:    console.log('Express server listening on port ' + app.get('port'));
422: });

これで、リクエストの待ち受けが始まる。