Search code examples
javascriptnode.jshttpserveraxios

Unable to send POST request to Node.js HTTP Webserver from anything other than localhost


I'm currently trying to send text/data from a client browser to a Node.js webserver, but whenever I use anything other than localhost, I receive a "POST ERR_CONNECTION_TIMED_OUT" error.

Here's the code for the client (html):

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Client</title>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  </head>
  <body>
    <textarea id="stream" rows="8" cols="80" oninput="update(this.value)"></textarea>
    <script>

      function update(v) {
        let data = {
          text: v
        };
        send(data);
      }

      function send(d) {
        axios.post(
          "http://<the server IP>:34567",
          JSON.stringify(d)
        ).then(res => {
          // do nothing
        }).catch(error => {
          console.error(error);
        });
      }
    </script>
  </body>
</html>

And the server code (node.js; http is installed as a node module):

const http = require("http");

const port = 34567;

http.createServer(async (req, res) => {

  const headers = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "OPTIONS, POST, GET",
    "Access-Control-Max-Age": 2592000,
    "Access-Control-Allow-Headers": "*"
  };

  switch (req.method) {
    case "GET":
      // Write success header:
      res.writeHead(200, headers);

      // Respond:
      res.write("not yet implemented");
      // Close:
      res.end();
      return;
    break;
    case "POST":

      const buffers = [];

      for await (const chunk of req) {
        buffers.push(chunk);
      }

      const data = Buffer.concat(buffers).toString();

      console.log(JSON.parse(data).text); // Should print the text from the textarea

      res.writeHead(200, headers);
      res.end();
      return;
    break;
    case "OPTIONS":
      res.writeHead(204, headers);
      res.end();
      return;
    break;
  }

  res.writeHead(405, headers);
  res.end(`${req.method} is not allowed for the request.`);

}).listen(port, () => {
  console.log(`[INFO] Server is active on port ${port}.`)
});

I've already port-forwarded the 34567 port (TCP).

Whenever I'm on the host computer, and I use "localhost" for the server IP, everything works fine. The text in the client textarea shows up instantly in the server console. However, when I change this to my actual IP (looks something like 12.34.567.890), everything stops working. I'll usually get the "POST ERR_CONNECTION_TIMED_OUT" error I mentioned earlier, but after a while, the server will (sometimes, not always) receive the data (I'm talking like... almost a minute later, which makes no sense).

What am I doing wrong here?

Any help is appreciated. Thanks.


Solution

  • It looks like you're not checking for routes, you're just checking the http method (post, get, etc..)

    The following example does a couple of things..

    • Lets you listen for specific routes
    • Lets you listen for specific http methods on those specific routes

    Imagine you have a folder with two files, server.js and home.html, as shown below. If you run node server.js and then open a browser and go to localhost:34567/home and open a browser console. From there, start typing. You should see data being logged client side as well as server side.

    The reason this is happening is because browsers are weird. If you don't serve the html file from a server, your browser has security features in place that prevent you from talking to your server (or any server). Basically, the url in your browser can't start with file:// when you're trying to communicate via XHR (axios/fetch/etc).

    server.js

    const http = require("http");
    const fs = require("fs");
    const fspath = require("path");
    const url = require("url");
    
    const server = http.createServer(async (request, response) => {
      const path = url.parse(request.url).pathname;
      switch (path) {
        /**
         * @route '/home' description for route
         * @methods (GET)
         */
        case "/home": {
          switch (request.method) {
            case "GET":
            default: {
              try {
                const html = await fs.readFileSync(fspath.resolve("./home.html"));
                response.writeHead(200);
                response.write(html);
              } catch (e) {
                console.error(e);
                response.writeHead(500);
                response.write("unable to read home html : " + e);
              }
              response.end();
            }
          }
        }
    
        /**
         * @route '/update'
         * @methods (POST)
         */
        case "/update": {
          switch (request.method) {
            case "POST": {
              console.log("in /update POST");
              const buffers = [];
              for await (const chunk of request) {
                buffers.push(chunk);
              }
              const data = Buffer.concat(buffers).toString();
              console.log(JSON.parse(data));
              response.end();
            }
            default: {
              response.end();
            }
          }
        }
        default: {
          response.end();
        }
      }
    });
    
    server.listen(34567);
    
    const serverAddress = server.address();
    const serverIp = serverAddress.address === "::" ? "localhost" : serverAddress.address;
    
    console.log(`Server listening at : ${serverIp}:${serverAddress.port} : go to http://localhost:34567/home`);
    

    home.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Client</title>
        <!-- script src="https://unpkg.com/axios/dist/axios.min.js"></script -->
      </head>
      <body>
        <textarea id="stream" rows="8" cols="80" oninput="update(this.value)"></textarea>
        <script>
          async function update(v) {
            let data = {
              text: v,
            };
            await send(data);
          }
    
          async function send(d) {
            try {
              const res = await fetch("/update", {
                method: "POST",
                body: JSON.stringify(d),
              });
              console.log(res);
            } catch (e) {
              console.error(e);
            }
            // ~
            // REMOVED : sorry, fetch was just easier..
            // ~
            // axios.post(
            //   "http://<the server IP>:34567",
            //   JSON.stringify(d)
            // ).then(res => {
            //   // do nothing
            // }).catch(error => {
            //   console.error(error);
            // });
          }
        </script>
      </body>
    </html>