도서관에 도서 요청

도서관에 책을 등록하는 프로그램 입니다. 기존에 JAVA로 만들어진 모듈을 nodejs로 변경 하였습니다.
JAVA 소스의 경우 selenium을 설치하고 설정해야하는 부분이 많았는데 puppeteer를 이용하면 간단하게 최소한의 설정으로 처리가 가능하다.

프로그램 설계

부천 시립 도서관에서 한달에 요청 할 수 있는 도서는 다음과 같다.

  • 상동, 심곡, 꿈빛, 책마루, 송내도서관 : 1인당 월 5권
  • 원미, 북부, 한울빛, 꿈여울도서관 : 1인당 월 20권

이에 도서 목록을 다음과 같이 TXT 파일로 작성하연 자동으로 등록 하도록 한다.

도서목록 파일 내용 샘플

도서관코드|도서명
AA|즐거운하루
AB|자동책등록프로그램

도서관 코드

2019년 부터 상동, 심곡, 꿈빛, 책마루, 송내, 동화도서관: 1인당 월 5권 으로 변경 되었습니다.

아래 내용은 2017년 기준 내용입니다.

도서관코드 도서관명 비고
AA 상동 월5권
AB 원미 월20권
AC 삼곡 월5권
AD 북부 월20권
AE 꿈빛 월5권
AF 책마루 월5권
AG 한울빛 월20권
AH 꿈여울 월20권
AI 송내 월5권
AK 도당 월20권

프로세스 순서도

프로세스 순서도

Notice

  • nodejs 필요
  • _env 를 .env 로 파일명 변경
  • .env 에 ID/PW 작성

실행 방법

# 모듈 설치
npm install

# 실행
npm run start

License

희망 도서 자동 등록 프로그램은 open source 프로그램으로 MIT 라이선스를 따릅니다.

This Request book apply is free and open source software, distributed under the MIT License. So feel free to use this program on your project without linking back to me or including a disclaimer.

Posted by lahuman

express에서 File Upload 구현 하기

업로드는 복잡하게 구현 하지 않고 모듈을 사용하면 쉽게 할 수 있다.

express-fileupload를 이용하면 된다.

# express 의 app.js 에서 다음과 같이 사용 
const fileUpload = require('express-fileupload');
const cors = require('cors');
const fs = require('fs');

app.use(cors());
app.use(fileUpload());

app.post('/upload', (req, res, next) => {
  let uploadFile = req.files.file
  const fileName = req.files.file.name
  uploadFile.mv(
    `${__dirname}/public/files/${fileName}`,
    function (err) {
      if (err) {
        return res.status(500).send(err);
      }

      res.json(JSON.parse(fs.readFileSync(`${__dirname}/public/files/${fileName}`, 'utf8')));
    }
  );
})

그게 어렵지 않게 한다.

참고자료

Posted by lahuman

google docs Sheets 연동 하기

이번에 진행되는 프로젝트에서 데이터를 Google Docs의 Sheets에 넣어 두고 해당 데이터를 활용하여 대시보드를 구현하게 되었다.

Google Sheets API 소개에도 잘 나와 있듯이, API를 활용하여 쉽게 이용할 수 있다.

Node.js Quickstart에서 제공되는 소스는 아래와 같다.

const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');

// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';

// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then call the Google Sheets API.
  authorize(JSON.parse(content), listMajors);
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) return getNewToken(oAuth2Client, callback);
    oAuth2Client.setCredentials(JSON.parse(token));
    callback(oAuth2Client);
  });
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getNewToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return console.error('Error while trying to retrieve access token', err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) return console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}

/**
 * Prints the names and majors of students in a sample spreadsheet:
 * @see https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
 * @param {google.auth.OAuth2} auth The authenticated Google OAuth client.
 */
function listMajors(auth) {
  const sheets = google.sheets({version: 'v4', auth});
  sheets.spreadsheets.values.get({
    spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms',
    range: 'Class Data!A2:E',
  }, (err, res) => {
    if (err) return console.log('The API returned an error: ' + err);
    const rows = res.data.values;
    if (rows.length) {
      console.log('Name, Major:');
      // Print columns A and E, which correspond to indices 0 and 4.
      rows.map((row) => {
        console.log(`${row[0]}, ${row[4]}`);
      });
    } else {
      console.log('No data found.');
    }
  });
}

참 좋은 세상이다!

참고자료

Posted by lahuman

NodeJs에서 Parse Error: HPE_HEADER_OVERFLOW 발생

x-ray(node moudle)를 이용해서 크롤링을 개발하고 있던 중, 다음과 같은 오류를 만나게 되었다.

(node:63533) UnhandledPromiseRejectionWarning: Error: Parse Error
    at Socket.socketOnData (_http_client.js:442:20)
    at Socket.emit (events.js:189:13)
    at addChunk (_stream_readable.js:284:12)
    at readableAddChunk (_stream_readable.js:265:11)
    at Socket.Readable.push (_stream_readable.js:220:10)
    at TCP.onStreamRead (internal/stream_base_commons.js:94:17)
     bytesParsed: 6545,
  code: 'HPE_HEADER_OVERFLOW',

이 오류는 3개의 사이트중 1개의 사이트에서만 발생하였다.

원인을 파악하기 위해 검색을 하다 다음과 같은 내용을 확인하였다.

Node.js의 http 80KB의 Header 크기 제한을 가지고 있고 만약 큰 Header 크기가 필요 하다면, 명령을 실행시 특정 argument를 추가 해야 한다.

해결책은 다음과 같다!

실행하는 node 명령와 함께 --max-http-header-size=크기 argument를 추가 해라

예제)

$> node --max-http-header-size=81000 app.js

에러가 안날때 까지 최대 http header 크기를 키우면 된다.

근데... 이건 에러나는 사이트가 문제 아닌가? 80KB를 넘는 헤더를 왜 넘기지??

참고자료

Posted by lahuman

nodejs package.json의 모듈 업데이트 하기!

npm-check-updates을 이용하여 package.json에 등록된 모듈들을 의존성에 알맞게 최신 버젼으로 업데이트 할 수 있다.

설치 후 실행은 다음과 같다.

# global로 npm-check-updates 설치
$> npm i -g npm-check-updates
# 프로젝트(package.json과 동일한) 디렉토리에서 다음 명령어 실행
$> ncu -u
Upgrading /Users/admin/myProject/package.json
[====================] 17/17 100%

 app-root-path     ^2.1.0  →   ^2.2.1
 cookie-parser     ~1.4.3  →   ~1.4.4
 debug             ~2.6.9  →   ~4.1.1
 dotenv            ^7.0.0  →   ^8.0.0
 express          ~4.16.0  →  ~4.16.4
 express-session  ^1.15.6  →  ^1.16.1
 helmet           ^3.16.0  →  ^3.17.0
 http-errors       ~1.6.2  →   ~1.7.2
 morgan            ~1.9.0  →   ~1.9.1
 puppeteer        ^1.13.0  →  ^1.15.0

Run npm install to install new versions.

# 업데이트된 결과를 설치
$> npm install

자주는 아니더라도, 가끔씩은 모듈을 업데이트 하자!

참고자료

Posted by lahuman

nodejs port forwarding 처리

https를 설정하고 나니, 기존 80 포트로 요청이 있을 경우, 443으로 redirect 하는 방법을 찾아보았다.

구글을 확인해보니, 다음과 같이 쉽게 처리 할 수 있었다.

// Redirect from http port 80 to https
var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(301, { "Location": "https://" + req.headers['host'] + req.url });
    res.end();
}).listen(80);

구글과 함께면 뭐든 쉽다.

참조 링크

Posted by lahuman

puppeteer에서 POST로 요청 날리기

puppeteer에서 request를 POST로 요청 하기 위해서는 다음과 같은 설정을 해야 합니다.

await page.setRequestInterception(true);
// create a flag to only modify the initial request
let reformatFirstRequest = true;

page.on('request', interceptedRequest => {
  if (reformatFirstRequest) {
    console.log('first-request');
    reformatFirstRequest = false;
    interceptedRequest.continue({
      method: 'POST',
      postData: JSON.stringify(data),
      headers: { 'Content-Type': 'application/json' }
    });
  } else {
    interceptedRequest.continue();
  }
});

interceptedRequest 를 한번만 하지 않으면 이후 해당 request에 대한 404 오류가 떨어질 수 있다.

끗~

참고 자료

Posted by lahuman

web push 구현하기

web push를 구현하기 위해서 web-push라는 모듈을 사용해야 한다.

구현 방법은 Sending Web Push Notifications from Node.js을 참고 하면 된다.
(추후 시간을 내어 코드를 정리해보려고 한다.)

오늘은 일단 중요한 몇가지 사항을 공유 하려고 한다.

  1. windows에서는 알람을 활성하 해두어야 알람을 받을 수 있다.
  2. 실제 서비스는 https만 가능하며, 인증서 또한 공식 인증서를 이용해야 된다.
  3. subscription 정보를 잘 관리 하면 계속 상대방이 차단하기 전까지 계속 알람을 보낼 수 있다.
  4. 동작 원리는 worker를 등록하여 상대방에게 push를 한다.

일단 오늘은 여기까지!!!

참조 링크

Posted by lahuman

얼마전 Node에서 비동기 처리를 순서대로 처리해야 하는 일이 있었다.

구글을 검색하다 async.waterfall in a For Loop in Node.js을 찾았다.

var async = require("async")
var users = []; // Initialize user array or get it from DB

async.forEachLimit(users, 1, function(user, userCallback){

    async.waterfall([
        function(callback) {
            callback(null, 'one', 'two');
        },
        function(arg1, arg2, callback) {
            // arg1 now equals 'one' and arg2 now equals 'two'
            callback(null, 'three');
        },
        function(arg1, callback) {
            // arg1 now equals 'three'
            callback(null, 'done');
        }
    ], function (err, result) {
        // result now equals 'done'
        console.log('done')
        userCallback();
    });


}, function(err){
    console.log("User For Loop Completed");
});

asyncasync, await인줄 알았는데, 비동기 처리에 사용하는 모듈 이었다.

70 여 가지 모듈을 지원하며, 예제도 잘 나와 있다.

이중 제어 관련하여 다음의 매소드 등이 제공된다.

  1. XXXLimit : 한번에 처리 하는 Worker 갯수 지정
  2. XXXSeries : 한개씩 처리
  3. waterfall : 여러 비동기 처리를 순차적으로 처리
  4. parallel : 콜렉션을 병렬 처리
  5. apply : 인수 처리
  6. map : 새로운 컬렉션을 생성

이 외에도 많은 기능이 제공된다.

Async provides around 70 functions that include the usual 'functional' suspects (map, reduce, filter, each…) as well as some common patterns for asynchronous control flow (parallel, series, waterfall…). All these functions assume you follow the Node.js convention of providing a single callback as the last argument of your asynchronous function -- a callback which expects an Error as its first argument -- and calling the callback once.

비동기 처리를 할때 사용하면 상당히 유용하다.

참고 자료

Posted by lahuman
TAG async, nodejs

Express에서 post호출 시 request의 body에서 undefined가 발생한다.

다음과 같이 호출 했는데 결과가 undefined일 경우는 body-parser를 설정하지 않아서 이다.

# post 호출시, body에 {test:'hello"} 를 함께 전송

#router 소스
router.post('/', (req, res) => {
  console.log(req.body); undefined 발생
  res.redirect('/');
});

처리 방법은 body-parser를 express에서 사용하도록 설정 하면된다.

# body-parser 설치
$> npm install --save body-parser

#app.js 소스
const express = require('express');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser().json()); //bodyparser 사용 설정

이후 만나는 문제는 body의 크기가 크다는 오류를 만난 수 있다. 이때는 body의 크기를 옵션 설정하면 된다.

const bodyParser = require('body-parser');
app.use(bodyParser.json({limit: '50mb'})); //body 의 크기 설정
app.use(bodyParser.urlencoded({limit: '50mb', extended: true})); //url의 크기 설정

추가로 express 4.16부터는 body-parser를 포함 하고 있다.

const express = require('express')
const app = express();
app.use(express.json())
app.post('/', (req, res) => {
  console.log(req.body)
})

알아두자

참고 자료


Posted by lahuman