diff --git a/README.md b/README.md
index 89ea4e77..4862f5df 100644
--- a/README.md
+++ b/README.md
@@ -22,15 +22,6 @@ pip install -r requirements.txt
python server.py
```
-### Haskell
-
-```sh
-cabal sandbox init
-cabal install --only-dependencies
-ghc Server.hs
-./Server
-```
-
### Ruby
```sh
ruby server.rb
@@ -46,12 +37,19 @@ php server.php
go run server.go
```
-### Lua
+### Perl
```sh
-go get github.com/xyproto/algernon
-# or brew install algernon
-algernon server.lua
+cpan Mojolicious
+perl server.pl
```
And visit . Try opening multiple tabs!
+
+## Changing the port
+
+You can change the port number by setting the `$PORT` environment variable before invoking any of the scripts above, e.g.,
+
+```sh
+PORT=3001 node server.js
+```
diff --git a/Server.hs b/Server.hs
deleted file mode 100644
index 9a5c747e..00000000
--- a/Server.hs
+++ /dev/null
@@ -1,62 +0,0 @@
--- This file provided by Facebook is for non-commercial testing and evaluation
--- purposes only. Facebook reserves all rights not expressly granted.
---
--- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
--- FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
--- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
--- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
-
-{-# LANGUAGE OverloadedStrings #-}
-
-module Main (main) where
-
-import Web.Scotty
-
-import Control.Monad (mzero)
-import Control.Monad.Trans
-import Network.Wai.Middleware.Static
-import Network.Wai.Middleware.RequestLogger (logStdoutDev)
-import Data.ByteString.Lazy (readFile, writeFile, fromStrict)
-import qualified Data.ByteString as BS (readFile)
-import Prelude hiding (readFile, writeFile)
-import Data.Aeson hiding (json)
-import Data.Text
-import Data.Maybe (fromJust)
-
-data Comment = Comment {
- commentText :: Text,
- author :: Text
- } deriving (Eq, Show, Ord)
-
-instance FromJSON Comment where
- parseJSON (Object v) = Comment <$>
- v .: "text" <*>
- v .: "author"
- parseJSON _ = mzero
-
-instance ToJSON Comment where
- toJSON (Comment ctext author) = object ["text" .= ctext, "author" .= author]
-
-
-main :: IO ()
-main = scotty 3000 $ do
-
- middleware $ staticPolicy (noDots >-> addBase "public")
- middleware logStdoutDev
-
- get "/" $ file "./public/index.html"
-
- get "/api/comments" $ do
- comments <- liftIO $ readFile "comments.json"
- json $ fromJust $ (decode comments :: Maybe [Comment])
-
- post "/api/comments" $ do
- comments <- liftIO $ BS.readFile "comments.json"
- let jsonComments = fromJust $ (decode $ fromStrict comments :: Maybe [Comment])
- author <- param "author"
- comment <- param "text"
- let allComments = jsonComments ++ [Comment comment author]
- liftIO $ writeFile "comments.json" (encode allComments)
- json allComments
diff --git a/comments.json b/comments.json
index 61f5ef60..28816136 100644
--- a/comments.json
+++ b/comments.json
@@ -1,10 +1,27 @@
[
{
+ "id": 1388534400000,
"author": "Pete Hunt",
"text": "Hey there!"
},
{
+ "id": 1420070400000,
"author": "Paul O’Shannessy",
"text": "React is *great*!"
+ },
+ {
+ "id": 1464988635157,
+ "author": "ben",
+ "text": "*abc*"
+ },
+ {
+ "id": 1464988636500,
+ "author": "ben",
+ "text": "*abc*"
+ },
+ {
+ "id": 1464988717637,
+ "author": "evil",
+ "text": "alert(1)"
}
-]
+]
\ No newline at end of file
diff --git a/package.json b/package.json
index e7491981..bf3360a0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,8 @@
{
"name": "react-tutorial",
"version": "0.0.0",
+ "private": true,
+ "license": "see LICENSE file",
"description": "Code from the React tutorial.",
"main": "server.js",
"dependencies": {
diff --git a/public/css/base.css b/public/css/base.css
index bf382be3..c8cc35f7 100644
--- a/public/css/base.css
+++ b/public/css/base.css
@@ -23,7 +23,7 @@ code {
font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
font-size: 12px;
margin: 0 2px;
- padding: 0px 5px;
+ padding: 0 5px;
}
h1, h2, h3, h4 {
@@ -35,9 +35,6 @@ h1, h2, h3, h4 {
h1 {
border-bottom: 1px solid #ddd;
font-size: 2.5em;
- font-weight: bold;
- margin: 0 0 15px;
- padding: 0;
}
h2 {
diff --git a/public/index.html b/public/index.html
index c6494446..eb201204 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5,11 +5,11 @@
React Tutorial
-
-
-
-
-
+
+
+
+
+
diff --git a/public/scripts/example.js b/public/scripts/example.js
index 7e7bf35d..6493fea9 100644
--- a/public/scripts/example.js
+++ b/public/scripts/example.js
@@ -12,7 +12,8 @@
var Comment = React.createClass({
rawMarkup: function() {
- var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
+ var md = new Remarkable();
+ var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
},
@@ -44,6 +45,10 @@ var CommentBox = React.createClass({
},
handleCommentSubmit: function(comment) {
var comments = this.state.data;
+ // Optimistically set an id on the new comment. It will be replaced by an
+ // id generated by the server. In a production application you would likely
+ // not use Date.now() for this and would have a more robust system in place.
+ comment.id = Date.now();
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.ajax({
@@ -55,6 +60,7 @@ var CommentBox = React.createClass({
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
+ this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
@@ -79,12 +85,9 @@ var CommentBox = React.createClass({
var CommentList = React.createClass({
render: function() {
- var commentNodes = this.props.data.map(function(comment, index) {
+ var commentNodes = this.props.data.map(function(comment) {
return (
- // `key` is a React-specific concept and is not mandatory for the
- // purpose of this tutorial. if you're curious, see more here:
- // http://facebook.github.io/react/docs/multiple-components.html#dynamic-children
-
+
{comment.text}
);
@@ -98,22 +101,40 @@ var CommentList = React.createClass({
});
var CommentForm = React.createClass({
+ getInitialState: function() {
+ return {author: '', text: ''};
+ },
+ handleAuthorChange: function(e) {
+ this.setState({author: e.target.value});
+ },
+ handleTextChange: function(e) {
+ this.setState({text: e.target.value});
+ },
handleSubmit: function(e) {
e.preventDefault();
- var author = this.refs.author.value.trim();
- var text = this.refs.text.value.trim();
+ var author = this.state.author.trim();
+ var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit({author: author, text: text});
- this.refs.author.value = '';
- this.refs.text.value = '';
+ this.setState({author: '', text: ''});
},
render: function() {
return (
);
diff --git a/react-scotty.cabal b/react-scotty.cabal
deleted file mode 100644
index a3024211..00000000
--- a/react-scotty.cabal
+++ /dev/null
@@ -1,28 +0,0 @@
--- Initial react-scotty.cabal generated by cabal init. For further
--- documentation, see http://haskell.org/cabal/users-guide/
-
-name: react-scotty
-version: 0.1.0.0
-synopsis: React Haskell code with Scotty
--- description:
-license: GPL-2
-license-file: LICENSE
-author: Sibi
-maintainer: sibi@psibi.in
--- copyright:
-category: Web
-build-type: Simple
--- extra-source-files:
-cabal-version: >=1.10
-
-executable react-scotty
- main-is: Server.hs
- -- other-modules:
- -- other-extensions:
- build-depends: base >=4.8 && <4.9,
- scotty, wai-extra,
- mtl, text, aeson,
- bytestring,
- wai-middleware-static
- -- hs-source-dirs:
- default-language: Haskell2010
diff --git a/server.go b/server.go
index 9eb5d7d2..934a4cfc 100644
--- a/server.go
+++ b/server.go
@@ -22,9 +22,11 @@ import (
"net/http"
"os"
"sync"
+ "time"
)
type comment struct {
+ ID int64 `json:"id"`
Author string `json:"author"`
Text string `json:"text"`
}
@@ -64,7 +66,7 @@ func handleComments(w http.ResponseWriter, r *http.Request) {
}
// Add a new comment to the in memory slice of comments
- comments = append(comments, comment{Author: r.FormValue("author"), Text: r.FormValue("text")})
+ comments = append(comments, comment{ID: time.Now().UnixNano() / 1000000, Author: r.FormValue("author"), Text: r.FormValue("text")})
// Marshal the comments to indented json.
commentData, err = json.MarshalIndent(comments, "", " ")
@@ -82,11 +84,13 @@ func handleComments(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Access-Control-Allow-Origin", "*")
io.Copy(w, bytes.NewReader(commentData))
case "GET":
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-cache")
+ w.Header().Set("Access-Control-Allow-Origin", "*")
// stream the contents of the file to the response
io.Copy(w, bytes.NewReader(commentData))
diff --git a/server.js b/server.js
index 1379adbb..b5a7218a 100644
--- a/server.js
+++ b/server.js
@@ -16,25 +16,56 @@ var express = require('express');
var bodyParser = require('body-parser');
var app = express();
+var COMMENTS_FILE = path.join(__dirname, 'comments.json');
+
app.set('port', (process.env.PORT || 3000));
app.use('/', express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
-app.get('/api/comments', function(req, res) {
- fs.readFile('comments.json', function(err, data) {
+// Additional middleware which will set headers that we need on each request.
+app.use(function(req, res, next) {
+ // Set permissive CORS header - this allows this server to be used only as
+ // an API server in conjunction with something like webpack-dev-server.
+ res.setHeader('Access-Control-Allow-Origin', '*');
+
+ // Disable caching so we'll always get the latest comments.
res.setHeader('Cache-Control', 'no-cache');
+ next();
+});
+
+app.get('/api/comments', function(req, res) {
+ fs.readFile(COMMENTS_FILE, function(err, data) {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
res.json(JSON.parse(data));
});
});
app.post('/api/comments', function(req, res) {
- fs.readFile('comments.json', function(err, data) {
+ fs.readFile(COMMENTS_FILE, function(err, data) {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
var comments = JSON.parse(data);
- comments.push(req.body);
- fs.writeFile('comments.json', JSON.stringify(comments, null, 4), function(err) {
- res.setHeader('Cache-Control', 'no-cache');
+ // NOTE: In a real implementation, we would likely rely on a database or
+ // some other approach (e.g. UUIDs) to ensure a globally unique id. We'll
+ // treat Date.now() as unique-enough for our purposes.
+ var newComment = {
+ id: Date.now(),
+ author: req.body.author,
+ text: req.body.text,
+ };
+ comments.push(newComment);
+ fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
res.json(comments);
});
});
diff --git a/server.lua b/server.lua
deleted file mode 100644
index 10df59fc..00000000
--- a/server.lua
+++ /dev/null
@@ -1,38 +0,0 @@
--- This file provided by Facebook is for non-commercial testing and evaluation
--- purposes only. Facebook reserves all rights not expressly granted.
---
--- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
--- FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
--- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
--- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
-
---
--- For use with Algernon / Lua
---
--- Project page: https://github.com/xyproto/algernon
--- Web page: http://algernon.roboticoverlords.org/
---
-
-handle("/api/comments", function()
-
- -- Set the headers
- content("application/javascript")
- setheader("Cache-Control", "no-cache")
-
- -- Use a JSON file for the comments
- comments = JFile("comments.json")
-
- -- Handle requests
- if method() == "POST" then
- -- Add the form data table to the JSON document
- comments:add(ToJSON(formdata(), 4))
- end
-
- -- Return the contents of the JSON file
- print(tostring(comments))
-
-end)
-
-servedir("/", "public")
diff --git a/server.php b/server.php
index 53c19104..e510136b 100644
--- a/server.php
+++ b/server.php
@@ -27,21 +27,25 @@
function routeRequest()
{
- $comments = file_get_contents('comments.json');
$uri = $_SERVER['REQUEST_URI'];
if ($uri == '/') {
echo file_get_contents('./public/index.html');
} elseif (preg_match('/\/api\/comments(\?.*)?/', $uri)) {
+ $comments = file_get_contents('comments.json');
if($_SERVER['REQUEST_METHOD'] === 'POST') {
$commentsDecoded = json_decode($comments, true);
- $commentsDecoded[] = ['author' => $_POST['author'],
- 'text' => $_POST['text']];
+ $commentsDecoded[] = [
+ 'id' => round(microtime(true) * 1000),
+ 'author' => $_POST['author'],
+ 'text' => $_POST['text']
+ ];
$comments = json_encode($commentsDecoded, JSON_PRETTY_PRINT);
file_put_contents('comments.json', $comments);
}
header('Content-Type: application/json');
header('Cache-Control: no-cache');
+ header('Access-Control-Allow-Origin: *');
echo $comments;
} else {
return false;
diff --git a/server.pl b/server.pl
new file mode 100644
index 00000000..c3212b9c
--- /dev/null
+++ b/server.pl
@@ -0,0 +1,38 @@
+# This file provided by Facebook is for non-commercial testing and evaluation
+# purposes only. Facebook reserves all rights not expressly granted.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+use Time::HiRes qw(gettimeofday);
+use Mojolicious::Lite;
+use Mojo::JSON qw(encode_json decode_json);
+
+app->static->paths->[0] = './public';
+
+any '/' => sub { $_[0]->reply->static('index.html') };
+
+any [qw(GET POST)] => '/api/comments' => sub {
+ my $self = shift;
+ my $comments = decode_json (do { local(@ARGV,$/) = 'comments.json';<> });
+ $self->res->headers->cache_control('no-cache');
+ $self->res->headers->access_control_allow_origin('*');
+
+ if ($self->req->method eq 'POST')
+ {
+ push @$comments, {
+ id => int(gettimeofday * 1000),
+ author => $self->param('author'),
+ text => $self->param('text'),
+ };
+ open my $FILE, '>', 'comments.json';
+ print $FILE encode_json($comments);
+ }
+ $self->render(json => $comments);
+};
+my $port = $ENV{PORT} || 3000;
+app->start('daemon', '-l', "http://*:$port");
diff --git a/server.py b/server.py
index 7b4920b9..03c6213d 100644
--- a/server.py
+++ b/server.py
@@ -10,24 +10,35 @@
import json
import os
+import time
from flask import Flask, Response, request
app = Flask(__name__, static_url_path='', static_folder='public')
app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html'))
+
@app.route('/api/comments', methods=['GET', 'POST'])
def comments_handler():
-
- with open('comments.json', 'r') as file:
- comments = json.loads(file.read())
+ with open('comments.json', 'r') as f:
+ comments = json.loads(f.read())
if request.method == 'POST':
- comments.append(request.form.to_dict())
+ new_comment = request.form.to_dict()
+ new_comment['id'] = int(time.time() * 1000)
+ comments.append(new_comment)
+
+ with open('comments.json', 'w') as f:
+ f.write(json.dumps(comments, indent=4, separators=(',', ': ')))
- with open('comments.json', 'w') as file:
- file.write(json.dumps(comments, indent=4, separators=(',', ': ')))
+ return Response(
+ json.dumps(comments),
+ mimetype='application/json',
+ headers={
+ 'Cache-Control': 'no-cache',
+ 'Access-Control-Allow-Origin': '*'
+ }
+ )
- return Response(json.dumps(comments), mimetype='application/json', headers={'Cache-Control': 'no-cache'})
if __name__ == '__main__':
- app.run(port=int(os.environ.get("PORT",3000)))
+ app.run(port=int(os.environ.get("PORT", 3000)), debug=True)
diff --git a/server.rb b/server.rb
index 4e9916b3..698f4339 100644
--- a/server.rb
+++ b/server.rb
@@ -11,7 +11,9 @@
require 'webrick'
require 'json'
-port = ENV['PORT'].nil? ? 3000 : ENV['PORT'].to_i
+# default port to 3000 or overwrite with PORT variable by running
+# $ PORT=3001 ruby server.rb
+port = ENV['PORT'] ? ENV['PORT'].to_i : 3000
puts "Server started: http://localhost:#{port}/"
@@ -23,9 +25,9 @@
if req.request_method == 'POST'
# Assume it's well formed
- comment = {}
+ comment = { id: (Time.now.to_f * 1000).to_i }
req.query.each do |key, value|
- comment[key] = value.force_encoding('UTF-8')
+ comment[key] = value.force_encoding('UTF-8') unless key == 'id'
end
comments << comment
File.write(
@@ -38,6 +40,7 @@
# always return json
res['Content-Type'] = 'application/json'
res['Cache-Control'] = 'no-cache'
+ res['Access-Control-Allow-Origin'] = '*'
res.body = JSON.generate(comments)
end