Node.js의 GET, POST 처리

먼저 express를 이용해 다음과 같이 서버를 생성합니다.

const express = require("express");
const app = express();

const table = [
    { name: "Jack", alias: "Monkey" },
    { name: "Shelly", alias: "Cat" },
    { name: "Toms", alias: "Dog" }
];

// 여기에 GET, POST를 처리하는 코드가 추가됨

app.use(express.static(__dirname + "/static"));
app.listen(3000);

4번 코드의 table은 DB 대신 사용하는 데이터 객체입니다. 그리고 10번 코드는 static이라는 디렉토리에 test.html 파일을 넣을 것인데.. 이를 웹브라우저에서 127.0.0.1:3000/test.html 처럼 접근할 수 있도록 하기 위함입니다. 이 test.html 파일이 우리가 만들 서버에 GET, POST 요청을 하는 클라이언트에 해당하는 코드입니다.

가장 먼저 table에 저장된 사용자의 name 목록을 얻어오는 names GET 처리에 대한 코드는 다음과 같습니다. 코드 추가는 10번에 하면 됩니다.

app.get("/names", (req, res) => {
    const names = table.map(item => {
        return { name: item.name };
    });
    res.status(200).json(names);
});

test.html에 입력할 클라이언트 코드는 다음과 같구요.

const url = "http://127.0.0.1:3000/names";
fetch(url, {
    method: "GET"
}).then(response => {
    return response.text();
}).then(text => {
    const data = JSON.parse(text);
    console.log(data);
}).catch(error => {
    console.warn(error);
});

웹브라우저의 콘솔에 표시되는 실행 결과는 다음과 같습니다.

(3) [{…}, {…}, {…}]
0: {name: 'Jack'}
1: {name: 'Shelly'}
2: {name: 'Toms'}
...

이번에는 Query String을 갖는 GET 요청입니다. 이름을 받아 별칭을 그 결과로 전달하는 처리에 대한 서버 코드입니다.

app.get("/alias", (req, res) => {
    const name = req.query.name;
    const result = table.find((item) => {
        return item.name === name;
    });

    if(result) {
        res.status(200).json({ alias: result.alias });
    } else {
        res.status(200).json({});
    }
});

클라이언트 코드는 다음과 같습니다.

const url = "http://127.0.0.1:3000/alias?name=Suzan";
fetch(url, {
    method: "GET",
}).then(response => {
    return response.text();
}).then(text => {
    const data = JSON.parse(text);
    console.log(data);
}).catch(error => {
        console.warn(error);
});

결과는 다음과 같습니다.

{alias: 'Cat'}

이제 POST 호출입니다. 서버에 새로운 사람을 추가하는 서버측 코드입니다.

app.use(express.json());

app.post("/add", (req, res) => {
    const item = req.body;

    if(item.name && item.alias) {
        table.push(item);
        res.sendStatus(200);
    } else {
        res.sendStatus(400);
    }
});

위의 서비스를 이용하는 클라이언트 코드는 다음과 같습니다.

const url = "http://127.0.0.1:3000/add";
fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name: "Suzan", alias: "고슴도치" })
}).then(response => {
    console.log(response.ok);
}).catch(error => {
    console.warn(error);
});

Node.js에서 Thread 사용

서버 개발 시에 Node.js를 사용할 때 스레드를 이용해 사용자의 요청을 동시에 처리할 수 있습니다.

먼저 서버에 대한 기본 코드입니다.

const express = require("express");
const app = express();

app.get("/", (req, res) => {
    let total = 0;
    for(let i=0; i<10000000000; i++) {
        total++;
    }

    res.status(200).json({total});
});

app.listen(3000);

웹브라우저에서 localhost:3000으로 접속하면 몇초간 응답이 없다가 결과가 표시됩니다. 이 몇초간의 무응답은 서버의 모든 것들이 이 하나의 클라이언트 요청을 처리하는데 전념한다는 점입니다. 이 몇초간 또 다른 클라이언트의 요청이 들어온다면... 서버는 응답은 커녕 듣지도(listen) 못합니다.

이 문제를 개선하기 위한 코드를 살펴보면.. 먼저 위의 코드를 다음처럼 변경합니다.

const express = require("express");
const app = express();
const { Worker } = require("worker_threads");

app.get("/", (req, res) => {
    const worker = new Worker("./worker.js");
    worker.on("message", (data) => {
        res.status(200).json({total: data});
    })
});

app.listen(3000);

worker_threads라는 모듈을 추가했고 클라이언트 요청에 대한 연산을 worker.js로 분리시켰습니다. worker.js가 바로 스레드가 수행하는 코드이고 완료되면 message라는 이벤트가 호출되어 그 결과가 전달됩니다. worker.js의 코드는 다음과 같습니다.

const { parentPort } = require("worker_threads");

let total = 0;
for(let i=0; i<10000000000; i++) {
    total++;
}

parentPort.postMessage(total);

무거운 연산이 완료되면 parentPort.postMessage를 통해 결과를 전달합니다.

Javascript, 전화번호 형식으로 변경하는 함수

사용자가 원하는 형식으로 전화를 입력했을때 정해진 형식(xx-xxx-xxxx)으로 변경해주는 함수입니다.

const formatPhoneNumber = (input) => {
    const cleanInput = input.replaceAll(/[^0-9]/g, "");
    let result = "";
    const length = cleanInput.length;

    if(length === 8) {
        result = cleanInput.replace(/(\d{4})(\d{4})/, '$1-$2');
    } else if(cleanInput.startsWith("02") && (length === 9 || length === 10)) {
        result = cleanInput.replace(/(\d{2})(\d{3,4})(\d{4})/, '$1-$2-$3');
    } else if(!cleanInput.startsWith("02") && (length === 10 || length === 11)) {
        result = cleanInput.replace(/(\d{3})(\d{3,4})(\d{4})/, '$1-$2-$3');
    } else {
        result = undefined;
    }

    console.log(`${input} -> ${result}`);

    return result;
}

테스트를 위해 다음 코드를 실행해 보면..

formatPhoneNumber("08032332333");
formatPhoneNumber("021231234");
formatPhoneNumber("(02)12351234");
formatPhoneNumber("63633221");
formatPhoneNumber("010-9543-3224");
formatPhoneNumber("0625252312");
formatPhoneNumber("03112341234");

결과는 다음과 같습니다.

021231234 -> 02-123-1234
08032332333 -> 080-3233-2333
021231234 -> 02-123-1234
(02)12351234 -> 02-1235-1234
63633221 -> 6363-3221
010-9543-3224 -> 010-9543-3224
0625252312 -> 062-525-2312
03112341234 -> 031-1234-1234

인지하지 못한 전화번호 형식이 있을 수 있으니 개선해서 사용하시면 됩니다.

웹 3D 라이브러리(Three.js)를 이용한 메타버스 환경 구축 및 인터랙티브 웹 개발

안녕하세요, GIS Developer 김형준입니다.

오는 5월 25일부터 3일간 메타버스 환경 구축 및 인터랙티브 웹 개발이라는 주제를 가지고 강의를 진행합니다. 메타버스 환경 구축은 Blender라는 3차원 모델링 툴을 사용하고 인터렉티브 웹 개발은 three.js 라이브러리를 활용합니다. Javascript을 이미 알고 있다는 가정 하에 Blender나 three.js를 전혀 모르시는 분들도 이해하실 수 있도록 진행할 계획입니다.

아래의 영상은 교육 내용 중 실습 예제 중 하나입니다.

교육장소는 서울 판교에 있는 메타버스 캠퍼스입니다. 교육비는 무료이지만 참여할 수 있는 인원 수에 제한이 있습니다. 참여 신청을 위한 링크는 아래의 이미지를 클릭하시면 됩니다. 많은 참여 바랍니다.

[THREE.JS] Blooming Earth

원하는 결과는 다음과 같습니다.

장면에 추가된 모델은 3개입니다. 지구 본체, 지구 주변의 푸른 빛(Blooming Light), 지구 주위의 하얀 작은 무수한 별들.

먼저 지구 본체에 대한 코드입니다.

const sphere = new THREE.Mesh(
    new THREE.SphereGeometry(5, 50, 50), 
    new THREE.ShaderMaterial({
        uniforms: {
            globeTexture: {
                value: new THREE.TextureLoader().load("data/earth.jpg")
            }
        },
        vertexShader: `
            varying vec2 vertexUV;
            varying vec3 vertexNormal;

            void main() {
                vertexUV = uv;
                vertexNormal = normalize(normalMatrix * normal);

                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            }
        `,
        fragmentShader: `
            uniform sampler2D globeTexture;

            varying vec2 vertexUV;

            void main() {
                vec4 color = texture2D(globeTexture, vertexUV);
                gl_FragColor = vec4(color.xyz, 1.);
            }
        `
    })
);

결과는 다음과 같습니다.

지구 가장자리가 어두워서 가장자리를 밝게 만들기 위해 위의 코드에서 fragmentShader 코드를 다음처럼 변경합니다.

fragmentShader: `
    uniform sampler2D globeTexture;

    varying vec2 vertexUV;
    varying vec3 vertexNormal;

    void main() {
        float intensity = 1.05 - dot(vertexNormal, vec3(0.,0.,1.));
        vec3 atmosphere = vec3(0.3, 0.6, 1.0) * pow(intensity, 1.5);

        vec4 color = texture2D(globeTexture, vertexUV);
        gl_FragColor = vec4(atmosphere + color.xyz, 1.);
    }
`,

결과는 다음과 같습니다.

지구 주변의 푸른 빛(Blooming Light)에 대한 코드입니다.

const atmosphere = new THREE.Mesh(
    new THREE.SphereGeometry(5, 50, 50),
    new THREE.ShaderMaterial({
        vertexShader: `
            varying vec3 vertexNormal;

            void main() {
                vertexNormal = normalize(normalMatrix * normal);
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            }
        `,
        fragmentShader: `
            varying vec3 vertexNormal;

            void main() {
                float intensity = pow(0.76 - dot(vertexNormal, vec3(0,0,1.)), 2.0);
                gl_FragColor = vec4(0.3, 0.6, 1.0, 1) * intensity;
            }
        `,
        transparent: true,
        blending: THREE.AdditiveBlending,
        side: THREE.BackSide
    })
);
atmosphere.scale.set(1.2, 1.2, 1.2);

결과는 다음과 같습니다.

이제 지구 주위의 하얀 작은 무수한 별들에 대한 코드입니다.

const starGeometry = new THREE.BufferGeometry();

const starVertices = [];
for(let i=0; i<10000; i++) {
    const x = (Math.random() - 0.5) * 1000; 
    const y = (Math.random() - 0.5) * 1000; 
    const z = (Math.random() - 0.5) * 1000; 
    starVertices.push(x, y, z);
}

starGeometry.setAttribute("position", new THREE.Float32BufferAttribute(starVertices, 3));

const starMaterial = new THREE.PointsMaterial({color: 0xffffff});
const stars = new THREE.Points(starGeometry, starMaterial);
this._scene.add(stars);

결과는 이 글의 가장 첫번째 이미지와 같습니다.