Skip to content

Instantly share code, notes, and snippets.

@willprice
Created March 22, 2017 14:36
Show Gist options
  • Save willprice/684291f147151db86f531fdec31b36be to your computer and use it in GitHub Desktop.
Save willprice/684291f147151db86f531fdec31b36be to your computer and use it in GitHub Desktop.
SWI-Prolog echo server with some JSON manipulation using websockets.
% INSTRUCTIONS
% =swipl echo-server.pl=
% =:- start_server.=
%
% Then navigate to http://localhost:3000 in your browser
:- module(echo_server,
[ start_server/0,
stop_server/0
]
).
:- use_module(library(http/thread_httpd)).
:- use_module(library(http/http_dispatch)).
:- use_module(library(http/http_files)).
:- use_module(library(http/websocket)).
% http_handler docs: http://www.swi-prolog.org/pldoc/man?predicate=http_handler/3
% =http_handler(+Path, :Closure, +Options)=
%
% * root(.) indicates we're matching the root URL
% * We create a closure using =http_reply_from_files= to serve up files
% in the local directory
% * The option =spawn= is used to spawn a thread to handle each new
% request (not strictly necessary, but otherwise we can only handle one
% client at a time since echo will block the thread)
:- http_handler(root(.),
http_reply_from_files('.', []),
[prefix]).
% * root(echo) indicates we're matching the echo path on the URL e.g.
% localhost:3000/echo of the server
% * We create a closure using =http_upgrade_to_websocket=
% * The option =spawn= is used to spawn a thread to handle each new
% request (not strictly necessary, but otherwise we can only handle one
% client at a time since echo will block the thread)
:- http_handler(root(echo),
http_upgrade_to_websocket(echo, []),
[spawn([])]).
start_server :-
default_port(Port),
start_server(Port).
start_server(Port) :-
http_server(http_dispatch, [port(Port)]).
stop_server() :-
default_port(Port),
stop_server(Port).
stop_server(Port) :-
http_stop_server(Port, []).
default_port(3000).
%! echo(+WebSocket) is nondet.
% This predicate is used to read in a message via websockets and echo it
% back to the client
echo(WebSocket) :-
ws_receive(WebSocket, Message, [format(json)]),
( Message.opcode == close
-> true
; get_response(Message.data, Response),
write("Response: "), writeln(Response),
ws_send(WebSocket, json(Response)),
echo(WebSocket)
).
%! get_response(+Message, -Response) is det.
% Pull the message content out of the JSON converted to a prolog dict
% then add the current time, then pass it back up to be sent to the
% client
get_response(Message, Response) :-
get_time(Time),
Response = _{message:Message.message, time: Time}.
.container {
width: 960px;
margin: auto;
}
.message > div {
margin-top: 1px;
margin-bottom: 1px;
border: 2px;
border-radius: 10px;
padding: 5px;
}
.message > .content {
display: inline-block;
background-color: #e5c2c0;
}
.message > .timestamp {
display: inline-block;
background-color: #0d5d56;
color: #fff;
margin-right: 10px;
}
#message-panel {
position: fixed;
left: 0px;
bottom: 0px;
width: 100%;
height: 30px;
padding-left: 50px;
}
const WS_PROTO = "ws://"
const WS_ROUTE = "/echo"
function log(topic, message) {
console.log('[' + topic + '] ' + message)
}
function wsMessageHandler(event) {
const payload = JSON.parse(event.data)
log("WS Response", "Received message: '" + event.data + "'")
const messages = document.getElementById("messages")
const message = document.createElement("div")
message.className = 'message'
const contentElement = document.createElement("div")
contentElement.className = 'content'
contentElement.appendChild(document.createTextNode(payload.message))
const timestampElement = document.createElement("div")
timestampElement.className = 'timestamp'
timestampElement.appendChild(document.createTextNode(new Date(payload.time*1000)))
message.appendChild(timestampElement)
message.appendChild(contentElement)
let child = messages.appendChild(message)
child.scrollIntoView()
}
function sendMessage(connection, message) {
log("Client", "sending message \"" + message + "\"")
connection.send(message)
}
function openWebSocket() {
connection = new WebSocket(WS_PROTO + window.location.host + WS_ROUTE)
connection.onerror = (error) => {
log("WS", error)
}
connection.onmessage = wsMessageHandler
return connection
}
document.addEventListener('DOMContentLoaded', (e) => {
const input_box = document.getElementById("input-message")
const input_button = document.getElementById("message-submit")
const connection = openWebSocket()
input_button.addEventListener("click", (event) => {
const payload = {
message: input_box.value
}
sendMessage(connection, JSON.stringify(payload))
})
log("OnLoad", "Add event listeners")
})
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SWI-Prolog echo websockets example</title>
<link rel="stylesheet" href="echo.css" type="text/css" media="screen" charset="utf-8">
<script charset="utf-8" src="echo.js"></script>
</head>
<body>
<div class="container">
<div id="messages"></div>
<div id="message-panel">
<input id="input-message" type="text">
<button id="message-submit" type="button">Submit</button>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment