В настоящее время я пишу небольшую реализацию HTTP-сервера на Python. Однако при создании сообщения с запросом я сталкиваюсь с проблемой, когда двоичное содержимое файла неправильно добавляется к моему ответному сообщению OK.
Вот предварительно отформатированный нормальный ответ:
OK = "HTTP/1.1 200 OK{} Content-Type:{}{}{}".format(CRLF, contentType,
CRLF, CRLF)
Когда сервер получает запрос на какой-либо ресурс (в данном случае это html-файл, изображение или mp3), он анализирует запрос и извлекает имя указанного ресурса. В частности, у меня проблемы с этой функцией:
def getContents(fname):
with open(fname, 'rb') as f:
return f.read()
Traceback:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run() File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "myServer.py", line 46, in processRequest
response = getRequest(request)
File "myServer.py", line 82, in getRequest
return OK + getContents(resource)
File "myServer.py", line 136, in getContents
return OK + f.read()
TypeError: must be str, not bytes
В других реализациях я видел, как эта операция (а именно, OK + чтение двоичного содержимого файла ) работала раньше, но я немного запутался в том, что делать дальше.
Вот полный исходный код для заинтересованных лиц (имейте в виду, что это намеренно элементарная реализация):
import sys, threading, os, socket, stat
from socket import *
contentType = None
CRLF = "\r\n"
METHOD_NOT_ALLOWED = "HTTP/1.1 405 METHOD NOT ALLOWED{}Allow: GET, HEAD, POST {}Connection: close{}{}".format(CRLF, CRLF, CRLF, CRLF)
OK = "HTTP/1.1 200 OK{} Content-Type:{}{}{}".format(CRLF, contentType, CRLF, CRLF)
NOT_FOUND = 'HTTP/1.1 404 NOT FOUND{}Connection: close{}{}'.format(CRLF, CRLF, CRLF)
FORBIDDEN = 'HTTP/1.1 403 FORBIDDEN{}Connection: close{}{}'.format(CRLF, CRLF, CRLF)
MOVED_PERMANENTLY = 'HTTP/1.1 301 MOVED PERMANENTLY{}Location: *redacted*{}Connection: close{}{}'.format(CRLF, CRLF, CRLF, CRLF)
contentType = None
def main():
if (len(sys.argv) == 1):
port = 9001
else:
if (len(sys.argv) == 2):
port = int(sys.argv[1])
elif (len(sys.argv) > 2):
print("Invalid number of arguments; Port number defaulted to 9001...\n")
port = 9001
host = "localhost"
#creates socket object; SOCK_STREAM for TCP
serverSock = socket(AF_INET, SOCK_STREAM)
#bind socket to host on specified port
serverSock.bind((host, port))
#listen on the socket with the maximum number of queued client connections set to 128
serverSock.listen(128)
print("Server is listening...\n")
while 1:
#block until a client connects to the designated local port
connectionSock, addr = serverSock.accept()
print("Client connection accepted; starting server thread...\n")
server = threading.Thread(target=processRequest, args=[connectionSock, addr[0]])
server.start()
def processRequest(connectionSock, srcAddress):
request = connectionSock.recv(4096).decode("utf-8")
print("######\nREQUEST:\n{}\n######".format(request))
method = ((request[0:4]).strip()).upper()
if method == "GET":
response = getRequest(request)
elif method == "POST":
response = postRequest(request)
elif method == "HEAD":
response = headRequest(request)
else:
response = METHOD_NOT_ALLOWED
connectionSock.send(bytes(response, "utf-8"))
connectionSock.shutdown(1)
connectionSock.close()
def headRequest(request):
resource = getResource(request)
path = os.path.join(".", resource)
if resource == *redacted*:
return MOVED_PERMANENTLY
elif not os.path.exists(resource):
return NOT_FOUND
elif not checkPerms(resource):
return FORBIDDEN
else:
getContentType(resource)
return OK
def getRequest(request):
headResponse = headRequest(request)
if headResponse == MOVED_PERMANENTLY:
return MOVED_PERMANENTLY + *redacted*
elif headResponse == NOT_FOUND:
return NOT_FOUND + getContents("404.html")
elif headResponse == FORBIDDEN:
return FORBIDDEN + getContents("403.html")
else:
resource = getResource(request)
getContentType(resource)
return OK + getContents(resource)
def postRequest(request):
linelist = request.strip().split(CRLF)
formInputs = linelist[-1].split("&")
eventname = formInputs[0][10:]
location = formInputs[1][9:]
starttime = (formInputs[2][10:]).replace("%3A", ":")
endtime = (formInputs[3][8:]).replace("%3A", ":")
day = formInputs[4][4:]
responseHTML = """
<html>
<head>
<title>Form Submission</title>
</head>
<body>
<h1>Following Form Data Submitted Successfully</h1>
<table>
<tr>
<td>eventname</td>
<td>{}</td>
</tr>
<tr>
<td>starttime</td>
<td type=time>{}</td>
</tr>
<tr>
<td>endtime</td>
<td type=time>{}</td>
</tr>
<tr>
<td>location</td>
<td>{}</td>
</tr>
<tr>
<td>day</td>
<td>{}</td>
</tr>
</table>
</body>
</html>
""".format(eventname, starttime, endtime, location, day)
response = OK + responseHTML
return response
def getResource(request):
linelist = request.strip().split(CRLF)
reqline = linelist[0]
rlwords = reqline.split()
return rlwords[1][1:]
def getContents(fname):
with open(fname, 'rb') as f:
return f.read()
def checkPerms(resource):
"""Returns True if resource has read permissions set on 'others'"""
stmode = os.stat(resource).st_mode
return (getattr(stat, 'S_IROTH') & stmode) > 0
def getContentType(resource):
splitResource = resource.split(".")
fileType = splitResource[1]
if fileType == "png" or fileType == "jpg":
contentType = "image/" + fileType
elif fileType == "mp3":
contentType = "audio/mpeg"
elif fileType == "css":
contentType = "text/css"
else:
contentType = "text/html"
return
main()
Поскольку многие ресурсы представляют собой простые html-файлы, я выполнял чтение вместо двоичного чтения, а затем добавлял строку в OK. Однако это не сработало для изображений или mp3-файлов по понятной причине. Я здесь относительно новичок, поэтому, пожалуйста, извините за несоблюдение надлежащего этикета (и обязательно указывайте также на упомянутые ошибки!). Будем очень благодарны любой помощи!