Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
302 views
in Technique[技术] by (71.8m points)

javascript - Getting data from/writing data to localhost with Express

I'm trying to create a webapp for a web art class using node (w/ npm) and express. The idea is to have the body of the site be all one color, but anyone can text the site a hexcode/CSS color at a Twilio number and the color of the site will instantly change to that color value.

Essentially how it works is the server receives a POST request from Twilio at http://example.com/message, which contains the body of the text message. It writes it to a temporary file at ~/app/.data/color.tmp, which is accessed by the client with a jQuery .get() call to http://example.com/color, which returns

So here's the problem: I got a version of the app working on glitch.me, so I know that this code can work, but I'm having a lot of trouble getting it to work on my domain. I installed the app and can start it with npm, and it successfully shows me the HTML page, but the Chrome devtools show the script is receiving a 403 when it tries to access /color. Also, new texts to my site aren't changing the color value in /.data/color.tmp. I thought it might be a permissions issue but I checked them and they seem fine.

Here's the server file and the script on the index.html page:

app/server.js

var express = require('express');
var bodyParser = require('body-parser');
var fs = require('fs');
var app = express();
app.use(bodyParser.urlencoded({extended: false})); 
var dataPath = '.data/color.tmp';


// set a new color (saves posted color to disk)
app.post("/message", function (request, response) {
  var dataStr = JSON.stringify(request.body.Body);
  fs.writeFile(dataPath, dataStr);
  response.end();
});

// get the saved color (reading from disk)
app.get("/color", function (request, response) {
  var dataStr = fs.readFileSync(dataPath).toString();
  response.send(JSON.parse(dataStr));
});

app.get("/", function (request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

var listener = app.listen(process.env.PORT, function () {
  console.log('listening on port ' + listener.address().port);
});

app/views/index.html

    <script>
      // checks server for color value and sets background
      function checkForColorChange() {
        $.get('/color', function getColorComplete(data) {
          document.body.style.backgroundColor = data;
            console.log(data);
        })
      }

      // Poll the server at 2000ms interval
      setInterval(checkForColorChange, 2000);

      checkForColorChange();
    </script>

Anyway, I feel like I must be missing something really obvious if it worked so easily on Glitch and won't on my website, but I've been stuck for a few days and am not making any progress! Any help would be so appreciated. Let me know if anything's unclear too.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

(See update below for a working example)

TL;DR - example:
imageDeploy to Heroku

Original answer

There are few problems with your code:

  1. you're not checking for errors
  2. you're using blocking functions
  3. you're implicitly relying on file permissions but you're not checking it
  4. you're using string concatenation instead of path.join to join paths
  5. you're constantly polling for new data instead of waiting for it to change
  6. you're not catching exceptions of functions that can raise exception
  7. you're not waiting for async operations to finish and you don't handle errors

The main problem that you're experiencing right now is most likely with the file permissions. The good news is that you don't need any file access for what you're doing and using files for that is not optimal anyway. All you need is to store the color in a variable if you don't need it it persist between server restarts - and even if you do then I would use a simple database for that.

For example:

// some initial value:
var color = '#ffffff';

app.post("/message", function (request, response) {
  var color = request.body.Body;
  response.end();
});

// get the saved color (reading from disk)
app.get("/color", function (request, response) {
  response.send(color);
});

app.get("/", function (request, response) {
  response.sendFile(__dirname + '/views/index.html');
});

var listener = app.listen(process.env.PORT, function () {
  console.log('listening on port ' + listener.address().port);
});

This is the first change that I would use - don't rely on the file system, permissions, race conditions etc.

Another problem that you had with your code was using blocking functions inside of request handlers. You should never use any blocking function (those with "Sync" in their name) except the first tick of the event loop.

Another improvement that I would make would be using WebSocket or Socket.io instead of polling for data on regular intervals. This would be quite easy to code. See this answer for examples:

A plus of doing that would be that all of your students would get the color changed instantly and at the same time instead of in random moments spanning 2 seconds.

Update

I wrote an example of what I was describing above.

The POST endpoint is slightly different - it uses /color route and color=#abcdef instead of /message and Body=... but you can easily change it if you want - see below.

Server code - server.js:

// requires removed for brevity

const app = express();
const server = http.Server(app);
const io = socket(server);

let color = '#ffffff';

app.use(bodyParser.urlencoded({ extended: false }));
app.use('/', express.static(path.join(__dirname, 'html')));

io.on('connection', (s) => {
  console.log('Socket.io client connected');
  s.emit('color', color);
});

app.post('/color', (req, res) => {
  color = req.body.color;
  console.log('Changing color to', color);
  io.emit('color', color);
  res.send({ color });
});

server.listen(3338, () => console.log('Listening on 3338'));

HTML page - index.html:

<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width, initial-scale=1">
<title>Node Live Color</title>
<link href="/style.css" rel=stylesheet>
</head>
<body>
<h1>Node Live Color</h1>
<script src="/socket.io/socket.io.js"></script>
<script src="/script.js"></script>
</body>
</html>

Style sheet - style.css:

body {
  transition: background-color 2s ease;
  background-color: #fff;
}

Client-side JavaScript - script.js:

var s = io();
s.on('color', function (color) {
  document.body.style.backgroundColor = color;
});

What is particularly interesting is how simple is the client side code.

For your original endpoint use this in server.js:

app.post('/message', (req, res) => {
  color = req.body.Body;
  console.log('Changing color to', color);
  io.emit('color', color);
  res.end();
});

Full example is available on GitHub:

I tested it locally and on Heroku. You can click this button to deploy it on Heroku and test yourself:

Deploy to Heroku

Enjoy.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...