在Python 3中将二进制附加到字符串(HTTP响应)

时间:2018-10-26 22:57:46

标签: html http server python-3.6

我目前正在用Python编写一个HTTP服务器的小型实现。但是,在生成请求消息时,遇到一个问题,其中文件的二进制内容未正确附加到我的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 (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 + 读取文件的二进制内容),但是对于从何处进行操作,我有些困惑。 / p>

这是所有有兴趣者的全部源代码(请记住,这是有意的基本实现):

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文件。我在这里相对较新,因此请原谅任何未能遵守适当礼节的人(并请务必指出失败之处!)。任何帮助将不胜感激!

2 个答案:

答案 0 :(得分:0)

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()

尝试使用connectionSock.send(bytes(response))。不论出于何种原因,有时在添加额外的编码时都会遇到上述错误。

答案 1 :(得分:0)

听起来与“计算机网络:自上而下方法”中的套接字编程分配足够接近。大声笑

这个想法是,一旦建立了TCP连接,服务器就可以多次将数据发送到TCP连接中。换句话说,您不必在字节数据后附加HTTP OK消息,而是可以执行以下操作:

<div class="grid-container">
  <div class="item1 item">1</div>
  <div class="item2 item">2</div>
  <div class="item3 item">3</div>  
  <div class="item4 item">4</div>
  <div class="item5 item">5</div>
  <div class="item6 item">6</div>
  <div class="item7 item">7</div>
  <div class="item8 item">8</div>
  <div class="item9 item">9</div>
</div>

在客户端,您应该知道如何首先读取HTTP消息,然后处理响应正文中的字节码。