Using websockets in telnet server in Python3 - python

I have been working on a game server written in python3. My goal is to keep the communication options on it very open so that multiple different clients can easily connect. Currently all communication has been through telnet using miniboa. I would like to have the option to allow for web based clients as well. It seems like the easiest option for that would be to allow websocket connections. I have been playing around with websockify which works, however I would prefer to not use a proxy if possible because then all the connections appear to come from the proxy. Ideally what I would like is something that I can put into my telnet server to recognize a websocket handshake request (as compared to the regular requests), return the proper handshake, then keep the connection going so the commands being sent/recieved through telnet and websockets are the same. I haven't been able to find anything that allows me to do this automatically so I have been experimenting around with writing my own code to recognize a websocket handshake and reply with a corresponding handshake. I have looked at many other posts and examples, epecially python websocket handshake (RFC 6455) which I modified and converted to the following test program
#!/usr/bin/env python3
from miniboa import TelnetServer
from base64 import b64encode
from hashlib import sha1
clientlist = []
def client_connects(client):
clientlist.append(client)
def client_disconnects(client):
clientlist.remove(client)
def process_clients():
for client in clientlist:
if client.active and client.cmd_ready:
total_cmd = client.get_command()
print("incoming = {}" .format(total_cmd))
if total_cmd.find(" ") != -1: # breaking apart incoming command
cmd, cmd_var = total_cmd.split(" ", 1)
else:
cmd = total_cmd
cmd_var = ""
if cmd == "Sec-WebSocket-Key:":
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
cmd_var = cmd_var + GUID
encoded = cmd_var.encode('utf-8')
response_key = b64encode(sha1(encoded).digest())
websocket_answer = (
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Accept: {key}\r\n\r\n',
)
handshake = '\r\n'.join(websocket_answer).format(key=response_key)
client.handshakestr = handshake
if cmd == "Upgrade:":
print("Sending handshake:")
print(client.handshakestr)
print("End of Handshake")
client.send(client.handshakestr)
server = TelnetServer(port=6112, on_connect=client_connects, on_disconnect=client_disconnects)
while True:
process_clients()
server.poll()
which seems to get me past the initial handshake but then it immediately drops. From the output it looks like the browser is expecting something further but I can't figure out what. For a client I am using the following code on firefox 29.0 I downloaded from http://opiate.github.io/SimpleWebSocketServer/
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
function init()
{
document.myform.url.value = "ws://localhost:8000/"
document.myform.inputtext.value = "Hello World!"
document.myform.disconnectButton.disabled = true;
}
function doConnect()
{
websocket = new WebSocket(document.myform.url.value);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt)
{
writeToScreen("connected\n");
document.myform.connectButton.disabled = true;
document.myform.disconnectButton.disabled = false;
}
function onClose(evt)
{
writeToScreen("disconnected\n");
document.myform.connectButton.disabled = false;
document.myform.disconnectButton.disabled = true;
}
function onMessage(evt)
{
writeToScreen("response: " + evt.data + '\n');
}
function onError(evt)
{
writeToScreen('error: ' + evt.data + '\n');
websocket.close();
document.myform.connectButton.disabled = false;
document.myform.disconnectButton.disabled = true;
}
function doSend(message)
{
writeToScreen("sent: " + message + '\n');
websocket.send(message);
}
function writeToScreen(message)
{
document.myform.outputtext.value += message
document.myform.outputtext.scrollTop = document.myform.outputtext.scrollHeight;
}
window.addEventListener("load", init, false);
function sendText() {
doSend( document.myform.inputtext.value );
}
function clearText() {
document.myform.outputtext.value = "";
}
function doDisconnect() {
websocket.close();
}
</script>
<div id="output"></div>
<form name="myform">
<p>
<textarea name="outputtext" rows="20" cols="50"></textarea>
</p>
<p>
<textarea name="inputtext" cols="50"></textarea>
</p>
<p>
<textarea name="url" cols="50"></textarea>
</p>
<p>
<input type="button" name=sendButton value="Send" onClick="sendText();">
<input type="button" name=clearButton value="Clear" onClick="clearText();">
<input type="button" name=disconnectButton value="Disconnect" onClick="doDisconnect();">
<input type="button" name=connectButton value="Connect" onClick="doConnect();">
</p>
</form>
</html>
So does anyone know either:
1. an easier way to use websockets with my telnet server?
2. what is wrong with my attempts at responding to a websocket connection?
3. if I should give up and settle for using a proxy with web connections?

Tornado has a nice compliant WebSocket handler. You can use that, or use it for reference when building yours.

Related

React frontend sending image to fastapi backend

Frontend = React, backend = FastApi.
How can I simply send an image from the frontend, and have the backend saving it to the local disk ?
I've tried different ways: in an object, in a base64 string, etc.
But I can't manage to deserialize the image in FastApi.
It looks like an encoded string, I tried writing it to a file, or decoding it, but with no success.
const [selectedFile, setSelectedFile] = useState(null);
const changeHandler = (event) => {setSelectedFile(event.target.files[0]); };
const handleSubmit = event => {
const formData2 = new FormData();
formData2.append(
"file",
selectedFile,
selectedFile.name
);
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' },
body: formData2 // Also tried selectedFile
};
fetch('http://0.0.0.0:8000/task/upload_image/'+user_id, requestOptions)
.then(response => response.json())
}
return ( <div
<form onSubmit={handleSubmit}>
<fieldset>
<label htmlFor="image">upload picture</label><br/>
<input name="image" type="file" onChange={changeHandler} accept=".jpeg, .png, .jpg"/>
</fieldset>
<br/>
<Button color="primary" type="submit">Save</Button>
</form>
</div>
);
And the backend:
#router.post("/upload_image/{user_id}")
async def upload_image(user_id: int, request: Request):
body = await request.body()
# fails (TypeError)
with open('/home/backend/test.png', 'wb') as fout:
fout.writelines(body)
I also tried to simply mimic the client with something like this:
curl -F media=#/home/original.png http://0.0.0.0:8000/task/upload_image/3
but same result...
----- [Solved] Removing user_id for simplicity.
The server part must look like this:
#router.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
out_path = 'example/path/file'
async with aiofiles.open(out_path, 'wb') as out_file:
content = await file.read()
await out_file.write(content)
And for some reason, the client part should not include the content-type in the headers:
function TestIt ( ) {
const [selectedFile, setSelectedFile] = useState(null);
const [isFilePicked, setIsFilePicked] = useState(false);
const changeHandler = (event) => {
setSelectedFile(event.target.files[0]);
setIsFilePicked(true);
};
const handleSubmit = event => {
event.preventDefault();
const formData2 = new FormData();
formData2.append(
"file",
selectedFile,
selectedFile.name
);
const requestOptions = {
method: 'POST',
//headers: { 'Content-Type': 'multipart/form-data' }, // DO NOT INCLUDE HEADERS
body: formData2
};
fetch('http://0.0.0.0:8000/task/uploadfile/', requestOptions)
.then(response => response.json())
.then(function (response) {
console.log('response')
console.log(response)
});
}
return ( <div>
<form onSubmit={handleSubmit}>
<fieldset>
<input name="image" type="file" onChange={changeHandler} accept=".jpeg, .png, .jpg"/>
</fieldset>
<Button type="submit">Save</Button>
</form>
</div>
);
}
Replying from to your comment: yes there is a simple snippet example of JS-Fastapi and it was answered by me not long ago.
The reason you are not able to access the file is fairly simple: the naming of the python parameters must match the keys of the formData object.
Instead, you are accessing the raw request, which does not make sense.
Simply change your python code as follows:
from fastapi import UploadFile, File
#router.post("/upload_image/{user_id}")
async def upload_image(user_id: int, file: UploadFile = File(...)):
# Your code goes here
which is well detailed in the official docs
https://fastapi.tiangolo.com/tutorial/request-files/?h=file
FYI
The following are the references to my answers (actually checking, I answered multiple questions related to this topic and should be enough to understand the basic problems one may fall into when uploading files)
How to send a file (docx, doc, pdf or json) to fastapi and predict on it without UI (i.e., HTML)?
How can I upload multiple files using JavaScript and FastAPI?
How to upload file using fastapi with vue? I got error unprocessable 422
Uploading an excel file to FastAPI from a React app

Use jquery and Flask to print input but print nothing

I just learn flask to use with jquery, just want to print out what i typed in , but it print nothing
here is html code
<body>
<input id="name-input" type="text" />
<button id="name-button">Submit Name</button>
<p id="greeting"></p>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$("#name-button").click(function (event) {
let message = {
name: $("#name-input").val()
}
$.post("http://10.0.0.4:5000/hello", JSON.stringify(message), function (response) {
$("#greeting").text(response.greeting);
console.log(response);
});
});
</script>
</body>
here is flask code:
from flask import request
from flask import jsonify
from flask import Flask
app = Flask(__name__)
#app.route('/hello',methods=['POST'])
def hello():
message = request.get_json(force=True)
name = message['name']
response = {
'greeting': 'Hello, ' + name + '!'
}
return jsonify(response)
when i click button, it print nothing!. please help, thank a lot
Two issues:
You're using the IP address shown in the example, which is very unlikely to point to your own PC on a network. More likely is 127.0.0.1:5000/hello, which would be localhost if you're running through the dev server. You can actually run the server across the network by providing the --host=0.0.0.0 flag to flask run. NOTE: This is only for the development server; you'll want to look at deployment options when you run this for real
Your route only accepts POST requests, but you're sending a GET request
The HTMl:
<body>
<input id="name-input" type="text" />
<button id="name-button">Submit Name</button>
<p id="greeting"></p>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script>
$("#name-button").click(function (event) {
let message = {
name: $("#name-input").val()
}
$.post("http://127.0.0.1:5000/hello", JSON.stringify(message), function (response) {
$("#greeting").text(response.greeting);
console.log(response);
});
});
</script>
</body>
The route:
#app.route('/hello',methods=['GET', 'POST'])
def hello():
message = request.get_json(force=True)
name = message['name']
response = {
'greeting': 'Hello, ' + name + '!'
}
return jsonify(response)

Raspberry Pi python website with plotly graph updates

I am currently working on a project from home where I have a network of Arduino's sending data (temp humidity etc.) to a raspberry pi. I want to make the rasp take the data and using plotly make a variety of graphs and then embed said graphs into a website that automatically updates at a set interval. I already have the network up and running I am just stuck on how to get the graphs on to a HTML page and have it update. I was considering just running a Python script that makes a webpage and re-write it with the new graphs every time. This seems highly inefficient so I was wondering if there was a better way of doing it?
Some time ago I had a very similar problem. A very simple solution was to use Python3's http.server to return a JSON with a time stamp and the temperature.
# !/usr/bin/env python3
from http.server import HTTPServer, BaseHTTPRequestHandler
import random
import json
import time
def send_header(BaseHTTPRequestHandler):
BaseHTTPRequestHandler.send_response(200)
BaseHTTPRequestHandler.send_header('Access-Control-Allow-Origin', '*')
BaseHTTPRequestHandler.send_header('Content-type:', 'application/json')
BaseHTTPRequestHandler.end_headers()
class MyRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
# returns the temperature
if self.path == '/temperature':
send_header(self)
self.wfile.write(bytes(json.dumps({'time': time.strftime('%H:%M:%S', time.gmtime()), 'temperature': random.randint(0, 100)}), 'utf-8'))
if __name__ == '__main__':
# start server
server = HTTPServer(('', 8099), MyRequestHandler)
server.serve_forever()
The data is then received via simple vanilla JavaScript and put into plotly. Every 1000 ms a request is sent to the server and the graph is updated accordingly.
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script>
var temperatures;
var temperatures_x = [];
var temperatures_y = [];
var server_url = "";
//basic request handler
function createRequest() {
var result = null;
if (window.XMLHttpRequest) {
// FireFox, Safari, etc.
result = new XMLHttpRequest();
if (typeof result.overrideMimeType != "undefined") {
result.overrideMimeType("text/xml"); // Or anything else
}
} else if (window.ActiveXObject) {
// MSIE
result = new ActiveXObject("Microsoft.XMLHTTP");
}
return result;
}
//gets the temperature from the Python3 server
function update_temperatures() {
var req = createRequest();
req.onreadystatechange = function () {
if (req.readyState !== 4) {
return;
}
temperatures = JSON.parse(req.responseText);
return;
};
req.open("GET", server_url + "/temperature", true);
req.send();
return;
}
//updates the graph
function update_graph() {
update_temperatures();
temperatures_x.push(temperatures.time)
temperatures_y.push(temperatures.temperature)
Plotly.newPlot('graph_t', [{x: temperatures_x, y: temperatures_y}]);
}
//initializes everything
window.onload = function () {
document.getElementById("url").onchange = function () {
server_url = document.getElementById("url").value;
};
server_url = document.getElementById("url").value;
//timer for updating the functions
var t_cpu = setInterval(update_graph, 1000);
};
</script>
</head>
<body>
<li>
URL and port<input type="text" id="url" value="http://localhost:8099">
</li>
<div class="plotly_graph" id="graph_t"></div>
</body>
</html>

Uploading a file in ajax to CherryPy

I am trying to upload many files at once to my CherryPy server.
I am following this tutorial that shows PHP code on the server side.
The JavaScript part is simple. Here is a summary of what it does:
function FileSelectHandler(e) {
var files = e.target.files || e.dataTransfer.files;
for (var i = 0, f; f = files[i]; i++) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "upload", true);
xhr.setRequestHeader("X_FILENAME", file.name);
xhr.send(file);
}
I translated the upload.php described in the tutorial into something like this:
def upload(self):
[...]
When the server receives the request I can see that cherrypy.request.headers['Content-Length'] == 5676
which is the length of the file I'm trying to upload, so I assume the whole file has been sent to the server.
How do I get the content of the file?
At its minimum it looks like the following. Tested in Firefox and Chromium. If you need to support legacy browsers I'd look at some JavaScript library for polyfills and fallback.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import shutil
import cherrypy
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 8,
}
}
class App:
#cherrypy.expose
def index(self):
return '''<!DOCTYPE html>
<html>
<head>
<title>CherryPy Async Upload</title>
</head>
<body>
<form id='upload' action=''>
<label for='fileselect'>Files to upload:</label>
<input type='file' id='fileselect' multiple='multiple' />
</form>
<script type='text/javascript'>
function upload(file)
{
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', function(event)
{
console.log('progess', file.name, event.loaded, event.total);
});
xhr.addEventListener('readystatechange', function(event)
{
console.log(
'ready state',
file.name,
xhr.readyState,
xhr.readyState == 4 && xhr.status
);
});
xhr.open('POST', '/upload', true);
xhr.setRequestHeader('X-Filename', file.name);
console.log('sending', file.name, file);
xhr.send(file);
}
var select = document.getElementById('fileselect');
var form = document.getElementById('upload')
select.addEventListener('change', function(event)
{
for(var i = 0; i < event.target.files.length; i += 1)
{
upload(event.target.files[i]);
}
form.reset();
});
</script>
</body>
</html>
'''
#cherrypy.expose
def upload(self):
'''Handle non-multipart upload'''
filename = os.path.basename(cherrypy.request.headers['x-filename'])
destination = os.path.join('/home/user', filename)
with open(destination, 'wb') as f:
shutil.copyfileobj(cherrypy.request.body, f)
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)

HTML form data not received by Tornado class

I'm using WebSockets in the Tornado Framework and can't get the data in a html form to be sent to a tornado class.
This is my code:
class MainHandler(tornado.web.RequestHandler):
event = []
def get(self):
self.render('main.html')
def post(self):
MainHandler.event = self.get_argument('event')
When I try and send event to a WebSocketHandler class. no data is received from the form:
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
print "tailing..."
db = Connection().blah
coll = db.blah_tail
event = MainHandler.event
print 'Filtered', event
'Filtered' just prints an empty list: "Filtered []".
The html form:
<form action="/" method="post">
<input type="text" name="event" />
<input type="submit" id="open" value="Submit Query" />
</form>
How could you send the form data to the WSHandler class?
Thanks
The js for creating the websocket:
<script>
$(document).ready(function() {
var ws;
$("#open").click(function(evt){
evt.preventDefault();
ws = new WebSocket("ws://" + "localhost" + ":" + "8888" + "/ws");
ws.onmessage = function(evt) $("#display").append(evt.data + "<br />");
ws.onclose = function(evt) {alert("Server connection terminated");};
});
});
</script>
Just like in the example from the Tornado documentation, I'll use a set for the WebSocket clients. Improving this is left as an exercise for the reader.
# clients listing on the WebSocket
clients = set()
class MainHandler(tornado.web.RequestHandler):
def get(self):
return self.render("index.html")
def post(self):
global clients
event = self.get_argument("event")
print "got event", event
if not clients:
print "No WebSockets, no point in querying the database"
return
for coordinate in self.get_coordinates(event):
for client in clients:
print "sending coordinate", coordinate, "to client", client
client.write_message(json.dumps(coordinate,
default=json_util.default))
def get_coordinates(self, event):
# replace with a real database query
for coordinate in ("No", "man's", "land"):
time.sleep(1)
yield coordinate
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
global clients
print "WebSocket opened..."
clients.add(self)
def on_close(self):
global clients
print "WebSocket closed..."
clients.remove(self)
The relevant part of the index.html template:
<script type="text/javascript">
$(document).ready(function() {
var ws;
// open WebSocket for getting the results
ws = new WebSocket("ws://" + location.host + "/ws");
ws.onmessage = function(evt) {
$("#display").append(evt.data + "<br>");
};
ws.onclose = function(evt) {alert("Server connection terminated");};
$("#open").click(function(evt) {
evt.preventDefault();
$.post("/", $("#eventForm").serialize());
});
});
</script>
</head>
<body>
<h1>Event follower</h1>
<h2>Enter the event you would like to follow</h2>
<form id="eventForm" action="/" method="post">
<input type="text" name="event" />
<input type="submit" id="open" value="Submit Query" />
</form>
<h2>Coordinates</h2>
<div id="display">
</div>
</body>
When the page is loaded, a WebSocket connection is made to the server to the WSHandler class and the client is added to the clients set. When the page is closed, the WebSocket connection is closed and the server will remove it from the set.
When the open submit button is clicked, the form will be submitted asynchronously using AJAX to MainHandler.post. The method will find out the coordinates related to that event and send them to the listening clients as they come it. The browser receives each coordinate and it appends it to the display div.
What is the handler of your function
MainHandler or WSHandler,
Only One of them call at a single time so your syntax
event = MainHandler.event won't produce any result for you.
If your objective is only to submit the form.
Then on Submit type of event you have to write a post or get function associated with your submit call in your JS, That will work with normal tornado.web.RequestHandler on server side.
Ref. tornado web socket chat example
I have updated the chat example :
$(document).ready(function() {
if (!window.console) window.console = {};
if (!window.console.log) window.console.log = function() {};
$("#messageform").live("submit", function() {
newMessage($(this));
return false;
});
$("#message").select();
}
});
function newMessage(form) {
var message = form.formToDict();
var disabled = form.find("input[type=submit]");
disabled.disable();
$.postJSON("URL", message, function(response) {
console.log(response);
});
}
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
jQuery.postJSON = function(url, args, callback) {
args._xsrf = getCookie("_xsrf");
$.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
success: function(response) {
if (callback) callback(eval("(" + response + ")"));
}, error: function(response) {
console.log("ERROR:", response)
}});
};
When you will call $("#message").submit() you will receive form data in you "URL" function
If you want's to use WSHandler then
Ref. example link will help you.
See if this helps.

Categories

Resources