Vanilla Node.js / JavaScript가있는 AJAX – 양식이없는 POST JSON

Oct 11 2020

node.js, ajax 및 비동기 코드를 처음 사용합니다. 나는 브라우저와 서버간에 json을 교환하는 작업 방법을 모았고 더 많은 경험을 가진 누군가가 내 요구에 적합한 접근 방식을 고려하는지 궁금합니다.

요구 사항 컨텍스트 :이 기술은 백 오피스의 데이터 업데이트에 사용됩니다. 처음에는 데이터베이스와 동일한 컴퓨터에 한 명의 사용자가 있습니다. 차라리 몇 명의 다른 사용자가 나중에 백엔드를 원격으로 업데이트 할 수 있도록 허용하는 것을 배제하고 싶지는 않지만이 경우 원격 위치를 염두에두고 설계된 더 간단한 화면을 사용합니다. 이것은 백 오피스이기 때문에 최신 Chrome 브라우저를 주장 할 수 있습니다.

나의 한계 : 저는 반 은퇴 형 웹 사이트를 개발 / 지원하는 일인 상점입니다. 저는 파트 타임이고 제 책임은 코딩을 넘어서서 기껏해야 일부 거래의 잭이 될 수는 있지만 마스터는 아닙니다.

아래의 "요구 사항"으로 바로 건너 뛰 거나 더 많은 컨텍스트를 원하면 다음을 읽으십시오.

이렇게하기 위해 도서관을 사용하지 않는 이유는 무엇입니까? : 나는 그렇게해야한다는 말을 듣는 것에 개방적이지만, 그것을 사용해야하는 강력한 이유가없는 한 도서관을 피하는 것을 선호합니다. 나는 몇 가지 일을 수행하는 방법을 파악한 다음 반복해서 자동화해야하는 1 인 상점입니다. 많은 일을하는 대기업과는 매우 다른 상황입니다. 예를 들어,이 응용 프로그램의 프런트 엔드에는 백 엔드에서 작성된 50 만 개의 정적 웹 페이지가 있습니다. 페이지 유형은 6 개 뿐이며 각 페이지 유형은 단일 히트로 서버에로드됩니다. 일반 바닐라 html, css 및 javascript로 작성됩니다. 라이브러리를 사용하거나 더 많은 시간을 할애 할 때 메뉴가 더 멋지게 보이지만 단순함에 가치가 있습니다. 라이브러리는 버전 제어를 복잡하게 만들고 일반적으로 사용하지 않을 엄청난 기능을 제공하며 필요하지 않은 레거시 지원을 끌어옵니다. 내 개인적인 경험은 다른 많은 제품에 비해 여러 제품이 인터페이스하는 방식을 기억하는 것보다 한 제품에 대해 조금 더 깊이 들어가는 것이 더 낫다는 것입니다.

도구 이론적 근거 : 사이트에서 수익을 창출 할 계획이 없으므로 MySQL과 node.js를 선택한 이유 중 하나는 취미에 대한 라이센스 비용이 없어서 20 년 동안 계속하고 싶습니다. 도구는 내 요구 사항에 부합하는 것 같고 인터넷에서 작업하는 방법을 찾을 수있을만큼 인기가 있습니다. 또한 파트 타임 1 인 상점으로서 서버와 브라우저에서 동일한 언어를 사용할 수 있다는 점은 엄청난 이점입니다.

요구 사항 : 일반 vanilla node.js를 사용하여 웹 사이트에 대한 백엔드 데이터 유지 관리를 지원하기 위해 브라우저와 서버간에 큰 json 파일을 이동합니다. 현재 솔루션을 테스트 한 결과 이동하는 데 필요한 것보다 더 빨리 이동하는 데 필요한 것보다 더 많은 데이터를 이동합니다. (저는 8Meg json 파일을 필요로하는 100,000 개의 객체를 두 가지 방법으로 테스트했습니다.) 그러나 저는 ajax 경험이 없으며 예측하지 못한 문제가 있거나 이런 종류의 ajax를 더 나은 방식으로 쉽게 수행 할 수있는 방법이있을 수 있습니다.

내가 고려한 문제 :

  • CORS는 ajax 요청에 응답하는 서버가 먼저 요청을 만드는 html 파일을 제공해야합니다.
  • Ajax POST는 멱 등성이 아닙니다.
  • 브라우저는 자동으로 파비콘을 호출합니다.

서버에서 각 유지 관리 페이지에 대한 사례를 계속 추가하면 switch 문이 너무 길어집니다. 나는 그것을 정리하고 당신이 제공하고자하는 일반적인 조언을 환영 할 것이다. 그러나 여기서 나의 질문은 ajax 기술이 나의 필요에 합당한 지 여부이다.

더 쉽게 읽을 수 있도록 코드의 색상 화 된 VSCode 이미지와 컴퓨터에 복사하여 실행할 수있는 실제 코드를 포함하고 있습니다. 샘플 코드는 .js와 .html 파일을 같은 디렉토리에 넣고 이름을 "test_ajax_post_json_sans_form"으로 지정하여 그대로 테스트 할 수 있습니다. 명령 콘솔에서 "node filename"을 실행 한 다음 http : // localhost를로드하여 테스트합니다. : 8000 / filename in your browser. I used Chrome.

클라이언트 코드 이미지 ORIGINAL (업데이트를위한 스 니펫 참조)이있는 html :

Node.js 이미지 :

스 니펫에 코드를 포함했지만 실행하려면 node.js가 필요하므로 node.js가있는 컴퓨터에 복사해야합니다. (제안 사항 10/11에 따라 HTML 업데이트 됨) :

'use strict';
const host = 'localhost';
const http = require('http'); // VSCode shows 3 dots under "require" and says something about NodeRequire???
const fs   = require('fs').promises;
const port = 8000;

const requestListener = function (req, res) {
    switch (req.url) {
        case "/test_ajax_post_json_sans_form.html": // serve an intial html file
            fs.readFile(__dirname + '/test_ajax_post_json_sans_form.html')
            .then(contents => {
                res.writeHead(200, {'Content-Type': 'text/html'});
                res.end(contents);
            })
            .catch(err => {
                res.writeHead(500);
                res.end(err);
                return;
            });
            break
        case '/test_ajax_post_json_sans_form.html/ajaxTest1': // receive json, process, then return other json
            let body = '';
            req.on('data', (data) => {
                body += data;
            });
            req.on('end', () => {
                console.log(body);  // to show all data has arrived
                // here we will check or errors, create a complex return-object, stringify it, and send it back
                const objToReturn = {data1: 'Test message from server', data2: 'could be a complex json object'};
                res.writeHead(200, {'Content-Type': 'application/json'});
                res.end(JSON.stringify(objToReturn));
            });         
            break
        case '/favicon.ico':
            // browser will call favicon automatically. This satisfies the request (though its failing
            //   won't keep the ajax from working).
            fs.readFile(__dirname + '/favicon.ico')
            .then(contents => {
                res.writeHead(200, {'Content-Type': 'image/x-icon'});
                res.end(contents);
            })
            .catch(err => { // lacking favicon will not impact test
                res.writeHead(200, {'Content-Type': 'image/x-icon'});
                res.end();
            });
            break
        default:
            res.writeHead(404);
            res.end(JSON.stringify({error:'Resource not found'})); // in production, might load a not-found page here
    }
} 
const server = http.createServer(requestListener);
server.listen(port,host, () => { // binds the server object to a newtwork address
    console.log(`Server is running on http://${host}:${port}. (^c to cancel)`);
});
<!DOCTYPE html><body>
    <button type="button" id="submitButton">Send and then Receive JSON</button>
    <br>After clicking the button, the JSON string sent from the browser to the server will show in the console.
    <br>Then the JSON response string from the server will replace what is below.
    <br><br>
    <div id='messageArea'>
        Message to replace via ajax.
    </div>

    <script>
        'use strict';
        const submitButton = document.getElementById('submitButton');
        const messageArea = document.getElementById('messageArea');
        async function exchangeJSON() {
            try {
                submitButton.disabled = true;
                // here can build a complex object to send
                const objToSend = {message1: 'Test message from browser', message2: 'could be a complex JSON string'};
                const response = await fetch('test_ajax_post_json_sans_form.html/ajaxTest1', {
                        method: 'POST',
                        body: JSON.stringify(objToSend)
                });
                if (response.ok) {
                    const jsonResponse = await response.json();
                    // here can parse, update screen, etc.
                    messageArea.textContent = JSON.stringify(jsonResponse);
                }
                //throw new Error('Test error in ExchangeJSON'); // uncomment for testing
            } catch (error) {
                messageArea.textContent = error;
            } finally {
                submitButton.disabled = false;
            }
        }

        submitButton.addEventListener('click',exchangeJSON);
    </script>
</body></html>

답변

3 CertainPerformance Oct 11 2020 at 05:19

고려fetch 코드가 보장 최신 브라우저에서는이에서 실행되고 fetch일반적으로 더 나은 선택보다는이있다 XMLHttpRequest- fetch(약속은 일반적으로 콜백 이상 작업 할 비트 좋네요) 약속-을 기반으로, 자사의 API는 비트 읽을 수있는 청소기 및 쓰기입니다 , 그리고 좀 더 간결합니다.

오류 처리 프런트 엔드에는 오류 처리가 없습니다. 어떤 이유로 든 요청이 실패하면 사용자에게 표시되지 않습니다. 버튼을 누르면 다시 비활성화되지 않고 영원히 처리되는 것처럼 보입니다. 중히 여기다

  • 오류가있는 경우 오류 메시지 표시 및
  • 오류가있는 경우 버튼 다시 활성화

응답 사용자에게 응답의 JSON을 표시하려면 다음을 수행하십시오.

  • innerHTML요소 로 응답을 설정하지 마십시오 . 이로 인해 임의의 코드 실행, 예기치 않은 추가 HTML 요소 및 HTML 엔티티와 관련된 이상한 일이 발생할 수 있습니다. .textContent대신 사용하십시오 .
  • 응답이 JSON이기 때문에 <pre>?

콜백 콜백이 인수를하지 않을 경우 실행할 콜백을하려는 경우, 리스너를 추가, 당신은 콜백을 전달할 수 있습니다 직접적 으로 addEventListener다른 함수에 포장 대신.

세미콜론 일부 줄에 세미콜론이 없습니다. 스타일이 일관되게하려면 사용하거나 사용하지 마십시오. 그렇지 않으면 전문가가되기를 바랍니다. 그렇지 않으면 자동 세미콜론 삽입 에 문제가 발생할 수 있습니다 . 스타일을 선택한 다음 linter 로 적용하십시오 .

idSubmitButton? 선택한 버튼의 변수 이름은 다음과 같아야 submitButton합니다. ID는 선택한 후에는 관련이 없습니다. 데 idid 속성의 접두사로하는 것은 이상도 어쩌면 그냥 사용하는 것입니다 submitButton.


<button type="button" id="submitButton">Send and then Receive JSON</button>
<br>After clicking the button, the JSON string sent from the browser to the server will show in the console.
<br>Then the JSON response string from the server will replace what is below.
<br><br>
<div class='error' style='color: red; display: none;'></div>
<pre>Response gets inserted here</div>
<script>
    'use strict';
    const submitButton = document.getElementById('submitButton');
    function exchangeJSON() {
        submitButton.disabled = true; // assure post isn't sent again prior to a response
        const testObjToSend = { message1: 'Test message from browser', message2: 'could be a complex JSON string' };
        const errorDiv = document.querySelector('.error');
        errorDiv.style.display = 'none'; // Hide previous error
        // If you want to cause an error if the transaction is taking way longer than expected,
        // see https://stackoverflow.com/q/46946380
        fetch(
            'ajaxTest1',
            {
                method: 'POST',
                body: JSON.stringify(testObjToSend),
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
            },
        )
            .then(res => res.text()) // if you wanted to expand error handling, could check if response is OK first
            .then((result) => {
                try {
                    // If it's not JSON, this will throw
                    JSON.parse(result);
                } catch(e) {
                    // Send text to catch handler below
                    throw new Error(result);
                }
                document.querySelector('pre').textContent = result;
            })
            .catch((error) => {
                errorDiv.style.display = 'block';
                errorDiv.textContent = JSON.stringify(error.message);
            })
            .finally(() => {
                submitButton.disabled = false; // post completed, so enable posting again
            });
    }
    submitButton.addEventListener('click', exchangeJSON);
</script>

백엔드로 :

DRY 코드로 정적 파일 제공 HTML 파일과 favicon 모두에 대한 요청 및 응답 핸들러는 현재 HTTP 서버에 하드 코딩되어 있습니다. 그것은 작동 할 수 있지만 성가신 양의 상용구 코드가 필요하며 다소 추합니다. 대신 제공 할 정적 파일이 4 개 또는 5 개 있는지 고려하십시오. 현재 방법은 확장 할 수 없습니다.

나는 그들을 사용하는 강력한 이유가없는 한 라이브러리를 피하는 것을 선호합니다

이것은 그것들을 사용하는 매우 강력한 이유입니다. 그들이 종종 신경 쓰지 않는 많은 기능과 함께 제공되는 것은 사실이지만, 그렇지 않으면 지루하게 구현해야 할 한두 개 또는 세 개의 사소하지 않은 기능에 대해 가치가 있습니다.

어느 것? Express를 추천하고 싶습니다. 그 사용은 매우 널리 퍼져 있으며 해당 사이트, Stack Overflow 및 인터넷의 다른 많은 곳에서 잘 문서화되어 있습니다.

경로를 다른 파일로 분리 하기 아래에서 하나를 제외하고 모두 리팩토링했지만 다른 논리를 처리해야하는 서버에 여러 비 정적 엔드 포인트가있는 일반적인 경우에는 다른 경로를 다른 파일로 분리하는 것이 좋습니다. 예를 들어, ajaxTest1요청 을 처리하는 함수를 내보내는 파일 하나 와 요청 을 처리하는 함수를 내보내는 다른 파일이있을 수 있습니다 login(예시처럼). 애플리케이션이 성장함에 따라 모든 것을 단일 파일에 넣는 것보다 훨씬 더 유지 관리가 용이합니다.

'use strict';
const port = 8000;
const express = require('express');
// Recommended to use compression if you're transferring large files:
// https://github.com/expressjs/compression
const compression = require('compression');

const app = express();
app
    .use(compression())
    // Put static files into the "public" directory:
    .use(express.static(__dirname + '/public'))
    // Parse JSON request bodies:
    .use(express.json())
    .post('/ajaxTest1', (req, res) => {
        console.log(req.body);
        res.status(200).json({ data1: 'Test message from server', data2: 'could be a complex json object' });
    })
    .listen(port);
console.log(`Server is running on http://localhost:${port}. (^c to cancel)`);