I can't figure out how to make this small http server reply the get request and play a sound in parallel.
Current code, does not close the get request until the sound "winsound.PlaySound("SystemExit", winsound.SND_ALIAS)" end playing.
What i need is for the sound to be async to the request so that the get request ends asap and the sound keeps playing.
#!/usr/bin/env python3
from http.server import HTTPServer, SimpleHTTPRequestHandler, test
import socketserver
from urllib.parse import urlparse
from urllib.parse import parse_qs
import sys
import winsound
class MyHttpRequestHandler(SimpleHTTPRequestHandler):
try:
def do_GET(self):
# Sending an '200 OK' response
self.send_response(200)
# Setting the header
self.send_header("Content-type", "text/html")
# Whenever using 'send_header', you also have to call 'end_headers'
self.end_headers()
html = "ok"
# Writing the HTML contents with UTF-8
self.wfile.write(bytes(html, "utf8"))
winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
return
except Exception as e:
print(str(e))
# Create an object of the above class
handler_object = MyHttpRequestHandler
PORT = 8000
my_server = socketserver.TCPServer(("", PORT), handler_object)
# Star the server
my_server.serve_forever()
Use a flag to run it async:
winsound.PlaySound("SystemExit", winsound.SND_ALIAS|winsound.SND_ASYNC)
If you wish to have tighter control, use concurrent.futures.ThreadPoolExecutor() to run it in a different thread:
from concurrent.futures import ThreadPoolExecutor
import winsound
pool = ThreadPoolExecutor()
class MyHttpRequestHandler(SimpleHTTPRequestHandler):
try:
def do_GET(self):
# Sending an '200 OK' response
self.send_response(200)
# Setting the header
self.send_header("Content-type", "text/html")
# Whenever using 'send_header', you also have to call 'end_headers'
self.end_headers()
html = "ok"
# Writing the HTML contents with UTF-8
self.wfile.write(bytes(html, "utf8"))
pool.submit(winsound.PlaySound, "SystemExit", winsound.SND_ALIAS)
return
except Exception as e:
print(str(e))
BACKGROUND
So basically, it's question about execution scope in Python. Back to your code above, in order for request to finish it has to be execute all task.
send response (this one doesn't make effect, because you have to return result from the method get)
setting headers
playing music
Obviously, you return at the end of the request execution and your thread monitor just one. So request will finish after finishing all the tasks.
SOLUTION
So as you mentioned in the question, you are right. To play music on your Server on the request, you have to run async task and let you request return result from get request. For ex. by using asyncio (it's very handy lib, so check it out)
import asyncio
import socketserver
from http.server import SimpleHTTPRequestHandler
import winsound
class MyHttpRequestHandler(SimpleHTTPRequestHandler):
try:
def do_GET(self):
self.send_response(200)
self.wfile.write(bytes("Ok", "utf8"))
# Shared Queue of Background Tasks
asyncio.get_event_loop().run_until_complete(
MyHttpRequestHandler.play_music()
)
except Exception as e:
print(str(e))
#staticmethod
async def play_music():
try:
print('Playing sound!')
winsound.PlaySound("SystemExit", winsound.SND_ALIAS)
# Maybe you want to add Error handling
finally:
pass
# Create an object of the above class
handler = MyHttpRequestHandler
server = socketserver.TCPServer(
("", 8000), handler)
# Star the server
server.serve_forever()
Related
I'm trying to write a python script that connects to a nodejs server using socket.io package. The server receives the events from the client and responds with other events. As an example, let's say that the client sends an "getHome" events and the server responds with a "homePage" event with some data. What I want is so be able to send an event with the client and block the execution of the script until the response is received, process the response and then do something else based on the server response. The code I wrote is:
#!/usr/bin/python3
import socketio
sio = socketio.Client()
#sio.event
def message(data):
print(data)
#sio.event
def homePage(data):
print(data)
sio.connect('http://docedit/socket.io/')
print("First call")
sio.emit("getHome")
print("Second call")
sio.emit("getHome")
The problem is that the second call to "emit" is done before receiving the response for the first one. The output of the script is something like:
First call
Second call
Welcome to Home <- response from the server
Welcome to Home <- response from the server
Reading the documentation, I tried to use "call" instead of "emit" but then the execution blocks forever, even if the homePage function executes normally:
#!/usr/bin/python3
import socketio
sio = socketio.Client()
#sio.event
def message(data):
print(data)
#sio.event
def homePage(data):
print(data)
sio.connect('http://docedit/socket.io/')
print("First call")
sio.call("getHome")
print("Second call")
sio.call("getHome")
Output:
First call
Welcome to Home <- response from the server
I didn't find an example with call so maybe I'm using it wrong...any help?
Best way is to use some kind of lock
from threading import Lock
lock_me = Lock()
lock_me.acquire() #ensure lock is acquire beforehand
import socketio
sio = socketio.Client()
#sio.event
def message(data):
print(data)
#sio.event
def homePage(data):
print(data)
lock_me.release()
sio.connect('http://docedit/socket.io/')
print("First call")
sio.emit("getHome")
lock_me.acquire()
print("Second call")
sio.emit("getHome")
Another way is with conditions or notify :)
I have a scenario where I need to first respond with HTTP 200 to a server request (due to a time limit) and then continue processing with the actual work.
I also can not use threads, processes, tasks, queues or any other method that would allow me to do this by starting a parallel "process".
My approach is to use the build in "Simple HTTP" server and I am looking for a way to force the server to respond with HTTP 200 and then be able to continue processing.
The current code will receive a POST request and print its content after a 3 seconds. I put a placeholder where I would like to send the response.
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
class MyWebServer(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
self.send_response_only(200)
self.end_headers()
# force server to send request ???
time.sleep(3)
print(post_data)
def run(server_class=HTTPServer, handler_class=MyWebServer, port=8000):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print('Starting httpd...')
httpd.serve_forever()
if __name__ == "__main__":
run()
I figured out a workaround solution. You can force the server to send a 200 OK and continue processing after with these two commands:
self.finish()
self.connection.close()
This solution is from this SO question: SimpleHTTPRequestHandler close connection before returning from do_POST method
However, this will apparently close the internal IO buffer that the server uses and it won't be able to server any additional requests after that.
To avoid running into an exception it works to terminate the program (which works for me). However this is just a workaround and I would still be looking for a solution that allows the server to keep processing new requests.
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
class MyHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
self.send_response_only(200)
self.end_headers()
self.finish()
self.connection.close()
time.sleep(3)
print(post_data)
quit()
def run(server_class=HTTPServer, handler_class=MyHandler, port=8000):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print('Starting httpd...')
httpd.serve_forever()
if __name__ == "__main__":
run()
After a lot of trouble to make a ros and a python based http server work, I have another problem to combine two different codes.
Here is what I have written so far to write a code that publish ros topics yet it respond to http server at the same time. The problem is that server is OK unless I replace
httpd.serve_forever()
with
httpd.service_actions
to make it non-blocking. Then the server does not respond. Any way to resolve this problem yet keeping the code non-blcoking?
#!/usr/bin/env python3
import rospy
from std_msgs.msg import String
from http.server import BaseHTTPRequestHandler, HTTPServer
class S(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
def do_GET(self):
self._set_headers()
self.wfile.write(b"<html><body><h1>hi!</h1></body></html>")
def do_HEAD(self):
self._set_headers()
def do_POST(self):
# Doesn't do anything with posted data
self._set_headers()
self.wfile.write(b"<html><body><h1>POST!</h1></body></html>")
def http_server_init(port,server_class=HTTPServer, handler_class=S):
server_address = ('', port)
httpd = server_class(server_address, handler_class)
print('Starting httpd...')
return (httpd)
def http_server_loop(httpd):
#httpd.serve_forever()
httpd.service_actions()
def talker_init():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(10) # 10hz
return (pub,rospy,rate)
def talker_loop(pub,rospy,rate):
hello_str = "hello world %s" % rospy.get_time()
rospy.loginfo(hello_str)
pub.publish(hello_str)
rate.sleep()
if __name__ == '__main__':
from sys import argv
if len(argv) == 2:
http_port=int(argv[1])
else:
http_port=8080
httpd=http_server_init(http_port)
try:
pub,rospy,rate=talker_init()
while not rospy.is_shutdown():
http_server_loop(httpd)
talker_loop(pub,rospy,rate)
except rospy.ROSInterruptException:
pass
By the way, in ROS, this code should be called via
rosrun <packagename> <script>.py
Calling from bash leads to an error. Unless you remove the ROS-related codes.
I am trying to write a simple multithreaded http server which answers requests after 5 sec.
This code does not work, two simultaneous requests take 10 sec to complete, and I don't understand why.
from socketserver import ThreadingMixIn
from http.server import SimpleHTTPRequestHandler, HTTPServer, BaseHTTPRequestHandler
class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
pass
import sys
import os
import time
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
time.sleep(5)
self.send_response(200)
self.send_header('Content-type','text/html')
self.end_headers()
self.wfile.write("answer")
return
server = ThreadingSimpleServer(('', 8000), Handler)
try:
while 1:
sys.stdout.flush()
server.handle_request()
except KeyboardInterrupt:
print("Finished")
You are calling handle_request, which handles one request after the other. You have to use serve_forever, so that the server can handle request automatically.
I'm trying to set up a HTTP server in a Python script. So far I got the server it self to work, with a code similar to the below, from here.
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
class MyHandler(BaseHTTPRequestHandler):
def do_GET(self):
print("Just received a GET request")
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write('Hello world')
return
def log_request(self, code=None, size=None):
print('Request')
def log_message(self, format, *args):
print('Message')
if __name__ == "__main__":
try:
server = HTTPServer(('localhost', 80), MyHandler)
print('Started http server')
server.serve_forever()
except KeyboardInterrupt:
print('^C received, shutting down server')
server.socket.close()
However, I need to get variables from the GET request, so if server.py?var1=hi is requested, I need the Python code to put var1 into a Python variable and process it (like print it). How would I go about this? Might be a simple question to you Python pros, but this Python beginner doesn't know what to do! Thanks in advance!
Import urlparse and do:
def do_GET(self):
qs = {}
path = self.path
if '?' in path:
path, tmp = path.split('?', 1)
qs = urlparse.parse_qs(tmp)
print path, qs
urlparse.parse_qs()
print urlparse.parse_qs(os.environ['QUERY_STRING'])
Or if you care about order or duplicates, urlparse.parse_qsl().
Import in Python 3: from urllib.parse import urlparse