A simple example of HTTP communication using the python3 socket module
To put it simply, HTTP is a rule (data is called TCP payload) for the part of TCP data (called TCP payload) that can be any data because HTML files are sent by TCP communication (not necessarily TCP). (Please follow this format), so you can send the data according to that format with recv, send.
Here, proceed while reading RFC7230, RFC7230 Japanese translation.
An HTTP message can be either a client-to-server request or a server-to-client response. The two can be distinguished from each other at this start line. The starting line is the request line in the former case and the [status line](https://help.amanohina. It takes the format com / RFC7230-ja.html#p.status-line).
This time we will use the following header fields.
In fact, I didn't use any header field values this time.
The values in the code and header clauses are fixed here, but as you know, they are determined dynamically.
RETURN_PHRASE = '\r\n'
VERSION = 'HTTP/1.1'
def make_request_line(mtd, request_target):
'''Return http_request-line'''
request_line = \
mtd + ' ' + request_target + ' ' + VERSION + RETURN_PHRASE
return request_line
def make_header_section():
'''Return http_header-section'''
field_values = {
'Host': '192.168.1.200',
'Accept': 'html/plain',
'Connection': 'close',
'User-Agent': 'Suzukaze Browser 1.0'
}
header_sc = 'Host:' + field_values['Host'] + RETURN_PHRASE
header_sc += 'Accept:' + field_values['Accept'] + RETURN_PHRASE
header_sc += 'Connection:' + field_values['Connection'] + RETURN_PHRASE
header_sc += 'User-Agent:' + field_values['User-Agent'] + RETURN_PHRASE
header_sc += RETURN_PHRASE
return header_sc
mtd = 'GET'
request_target = '/'
request_line = make_request_line(mtd, request_target)
header_section = make_header_section()
print(header_section)
The function make_request_line (method, request target)-> request line
returns:
GET / HTTP/1.1
The function make_header_section ()-> header section
returns:
Host:192.168.1.200
Accept:html/plain
Connection:close
User-Agent:Suzukaze Browser 1.0
Blank line
The code is fixed at 200. So reason_phrases
is superfluous but added for the mood.
RETURN_PHRASE = '\r\n'
VERSION = 'HTTP/1.1'
def make_status_line():
'''Return http_staus_line'''
reason_phrases = {
'200': 'OK',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed'
}
status_line = \
VERSION + ' ' + '200' + ' ' + reason_phrases['200'] + RETURN_PHRASE
return status_line
def make_header_section(content_length):
'''Return http_header_section'''
field_values = {
'Server': 'Suzukaze Server 1.0',
'Date': datetime.now(timezone.utc).strftime(r'%a, %d %b %Y %X %Z'),
'Connection': 'close',
'Content-Type': 'html/plain',
'Content-Length': content_length
}
header_sc = 'Date:' + field_values['Date'] + RETURN_PHRASE
header_sc += 'Server:' + field_values['Server'] + RETURN_PHRASE
header_sc += 'Connection' + field_values['Connection'] + RETURN_PHRASE
header_sc += 'Content-Type' + field_values['Content-Type'] + RETURN_PHRASE
header_sc += 'Content-Length' + field_values['Cotent-Length'] + RETURN_PHRASE
header_sc += RETURN_PHRASE
return header_sc
def read_index_html():
'''Return index.html and that length'''
with open('index.html', 'rb') as f:
index_html = f.read()
index_len = len(index_html)
return index_html, index_len
status_line = make_status_line()
index_html, index_len = read_index_html()
content_length = index_len
header_section = make_header_section(content_length)
make_status_line ()-> status line
returns:HTTP/1.1 200 OK
make_header_section (Content-Length value)-> header section
returns:Date:Mon, 00 Nov 2020 00:00:00 UTC
Server:Suzukaze Server 1.0
Connection:close
Content-Type:html/plain
Content-Length:1224
Blank line
read_index_html ()-> the contents of index.html, the length of index.html
returns:b"<!DOCTYPE html>\n<head>\n <meta charset='utf-8'>\n</head>\n<style>\n*
(Abbreviation)
ab\xe3\x81\xa4\xe3\x81\x84\xe3\x81\xa6</li>\n <li>\xe5\x95\x8f\xe3\x81\x84\xe5\x90\x88\xe3\x82\x8f\xe3\x81\x9b</li>\n </ul>\n </footer>\n </div>\n</body>\n"
1224
In this code, we know that the request is a'/', so we don't need a request-target, and we also close the Connection, so we don't have the information that the server needs after all. So the request line and header clause are passed through.
Even if we say that we implement the message body on the server side, we have already read index.html as a byte string, so we just attach it to the end of the header clause on the server side. There is no message body on the client side.
It should be noted here that the start line and header clause are Unicode character strings (str type). Since it is necessary to make it a byte-like-object to send it with a socket, convert it.
mtd = 'GET'
request_target = '/'
request_line = make_request_line(mtd, request_target)
header_section = make_header_section()
http_msg = \
bytes(request_line, 'utf-8') + bytes(request_line, 'utf-8')
status_line = make_status_line()
index_html, index_len = read_index_html()
msg_body = index_html
content_length = str(index_len)
header_section = make_header_section(content_length)
http_msg = \
bytes(status_line, 'utf-8') + bytes(header_section, 'utf-8') + msg_body
All you have to do now is send this HTTP message to each other.
The code here is based on TCP communication using Socket module-Python3.
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(SERVER_ADDRESS)
sock.listen(0)
try:
conn_sock, client_address = sock.accept()
print_msg('ac', 'The connection accepted.')
print_msg('i', '{}:{} --------> {}:{}'
.format(client_address[0], client_address[1],
SERVER_ADDRESS[0], SERVER_ADDRESS[1]))
# Receiving the request
recd_data = b''
while True:
data = conn_sock.recv(32)
if not data:
break
recd_data += data
print_msg('i', 'Total received: {}'.format(recd_data))
# Sending the response
http_msg = make_http_msg()
remaining = len(http_msg)
res = 0
while remaining > 0:
res = conn_sock.send(http_msg[res:])
remaining -= res
finally:
conn_sock.shutdown(socket.SHUT_WR)
conn_sock.close()
print_msg('cl', 'The connection closed.')
You can declare that you will not send any more data to the server side by shutting down after sending the request. (Empty bytes are sent at the socket level, so the server can detect the end of the request by receiving an empty byte.)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect(SERVER_ADDRESS)
print_msg('cn', 'The connection accepted')
# Sending the request
http_msg = make_http_msg()
remaining = len(make_http_msg())
res = 0
while remaining > 0:
res = sock.send(http_msg[res:])
remaining -= res
sock.shutdown(socket.SHUT_WR)
# Receiving the response
recd_data = b''
while True:
data = sock.recv(32)
print(data)
if not data:
break
recd_data += data
finally:
sock.close()
print_msg('cl', 'The connection closed.')
For the time being, the client side needs to take out the following two.
STS_LINE_PTN = b'''
(?P<version>HTTP/[0-9][.][0-9])[ ]
(?P<status_code>[0-9][0-9][0-9])[ ]
(?P<reason>([!\"#$%&\'()*+,-./0-9:;<=>?@A-Z[\\]^_`a-z{|}~ ]|\t)+)
([\r][\n])
'''
re.VERBOSE
at compile time. Doing so will ignore whitespace that is not in the character class []
.It is taken out as follows.
{'version': b'HTTP/1.1', 'status_code': b'200', 'reason': b'OK'}
b'\ r \ n \ r \ n'
and set that index to start of[start: end]
. To generate a new (message body only) byte string. Then decode it to get the original html code. That is, it is equal to the following character string (html_source) on the server side.with open('sample.html', 'r') as f:
html_source = f.read()
So far, there is no information required on the server side.
Here we make a simple html viewer using Pyqt5. The UI is created using a designer, converted to Python3 code with PyQt5 UI code generator 5.15.1
, and the code is tampered with. Originally, the logic added by the user is written in a class created separately so that there is no problem even if the UI is rewritten, but this time the UI will not be rewritten, so set it as it is. The browser looks like this: Import this module with the main module that performs socket communication.
browser_app.py
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView
class Ui_dialog(object):
def setupUi(self, dialog, html_source):
dialog.setObjectName('dialog')
dialog.resize(1000, 650)
self.stackedWidget = QtWidgets.QStackedWidget(dialog)
self.stackedWidget.setGeometry(QtCore.QRect(10, 50, 980, 590))
self.stackedWidget.setObjectName('stackedWidget')
self.page_1 = QtWidgets.QWidget()
self.page_1.setObjectName('page_1')
self.webView = QWebView(self.page_1)
self.webView.setGeometry(QtCore.QRect(0, 0, 980, 590))
self.webView.setObjectName('webView')
self.stackedWidget.addWidget(self.page_1)
self.page_2 = QtWidgets.QWidget()
self.page_2.setObjectName('page_2')
self.page_2_title = QtWidgets.QLabel(self.page_2)
self.page_2_title.setGeometry(QtCore.QRect(370, 60, 240, 50))
font = QtGui.QFont()
font.setPointSize(24)
self.page_2_title.setFont(font)
self.page_2_title.setObjectName('page_2_title')
self.page_2_label1 = QtWidgets.QLabel(self.page_2)
self.page_2_label1.setGeometry(QtCore.QRect(435, 230, 110, 22))
font = QtGui.QFont()
font.setPointSize(16)
self.page_2_label1.setFont(font)
self.page_2_label1.setObjectName('page_2_label1')
self.page_2_ip = QtWidgets.QLineEdit(self.page_2)
self.page_2_ip.setGeometry(QtCore.QRect(435, 262, 110, 28))
font = QtGui.QFont()
font.setPointSize(12)
self.page_2_ip.setFont(font)
self.page_2_ip.setObjectName('page_2_ip')
self.page_2_label2 = QtWidgets.QLabel(self.page_2)
self.page_2_label2.setGeometry(QtCore.QRect(468, 310, 42, 16))
font = QtGui.QFont()
font.setPointSize(16)
self.page_2_label2.setFont(font)
self.page_2_label2.setObjectName('page_2_label2')
self.page_2_url = QtWidgets.QLineEdit(self.page_2)
self.page_2_url.setGeometry(QtCore.QRect(335, 336, 310, 28))
font = QtGui.QFont()
font.setPointSize(12)
self.page_2_url.setFont(font)
self.page_2_url.setObjectName('page_2_url')
self.page_2_save = QtWidgets.QPushButton(self.page_2)
self.page_2_save.setGeometry(QtCore.QRect(425, 384, 130, 40))
font = QtGui.QFont()
font.setPointSize(18)
self.page_2_save.setFont(font)
self.page_2_save.setObjectName('page_2_save')
self.stackedWidget.addWidget(self.page_2)
self.url_lineEdit = QtWidgets.QLineEdit(dialog)
self.url_lineEdit.setGeometry(QtCore.QRect(10, 10, 666, 30))
font = QtGui.QFont()
font.setPointSize(12)
self.url_lineEdit.setFont(font)
self.url_lineEdit.setObjectName('url_lineEdit')
self.search_btn = QtWidgets.QPushButton(dialog)
self.search_btn.setGeometry(QtCore.QRect(670, 10, 90, 30))
font = QtGui.QFont()
font.setPointSize(12)
self.search_btn.setFont(font)
self.search_btn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.search_btn.setObjectName('search_btn')
self.config_btn = QtWidgets.QPushButton(dialog)
self.config_btn.setGeometry(QtCore.QRect(780, 10, 60, 30))
font = QtGui.QFont()
font.setPointSize(10)
self.config_btn.setFont(font)
self.config_btn.setObjectName('config_btn')
# ----------------------------------------------
self.url_lineEdit.setText('in preparation')
self.page_2_title.setText('Setting the DNS')
self.page_2_label1.setText('IP Address')
self.page_2_ip.setText('in preparation')
self.page_2_label2.setText('URL')
self.page_2_url.setText('in preparation')
self.webView.setHtml(html_source)
# ----------------------------------------------
QtCore.QMetaObject.connectSlotsByName(dialog)
def mybrowser(html_source):
app = QtWidgets.QApplication(sys.argv)
dialog = QtWidgets.QDialog()
ui = Ui_dialog()
ui.setupUi(dialog, html_source)
dialog.show()
sys.exit(app.exec_())
Put this in the same directory, import it on the client side and call it as follows.
status_dict, res_msg = status_line_parser(recd_data)
if status_dict['status_code'].decode() == '200':
html_lines = get_msg_body(res_msg)
html_lines = html_lines.decode()
mybrowser(html_lines)
Then
It is displayed like this.
client.py
client.py
import socket
import re
from colored_print import print_msg
from browser_app import mybrowser
# Request
SERVER_ADDRESS = ('192.168.1.201', 8000)
RETURN_PHRASE = '\r\n'
VERSION = 'HTTP/1.1'
def make_request_line(mtd, request_target):
'''Return http_request-line'''
request_line = \
mtd + ' ' + request_target + ' ' + VERSION + RETURN_PHRASE
return request_line
def make_header_section():
'''Return http_header-section'''
field_values = {
'Host': '192.168.1.200',
'Accept': 'html/plain',
'Connection': 'close',
'User-Agent': 'Suzukaze-Browser-1.0'
}
header_sc = 'Host:' + field_values['Host'] + RETURN_PHRASE
header_sc += 'Accept:' + field_values['Accept'] + RETURN_PHRASE
header_sc += 'Connection:' + field_values['Connection'] + RETURN_PHRASE
header_sc += 'User-Agent:' + field_values['User-Agent'] + RETURN_PHRASE
header_sc += RETURN_PHRASE
return header_sc
def make_http_msg():
'''Return http-message'''
mtd = 'GET'
request_target = '/'
request_line = make_request_line(mtd, request_target)
header_section = make_header_section()
http_msg = \
bytes(request_line, 'utf-8') + bytes(header_section, 'utf-8')
return http_msg
# Response
def status_line_parser(msg_lines):
'''Return dict of status line and http_msg except status line'''
STS_LINE_PTN = b'''
(?P<version>HTTP/[0-9][.][0-9])[ ]
(?P<status_code>[0-9][0-9][0-9])[ ]
(?P<reason>([!\"#$%&\'()*+,-./0-9:;<=>?@A-Z[\\]^_`a-z{|}~ ]|\t)+)
([\r][\n])
'''
p_sts_line = re.compile(STS_LINE_PTN, re.VERBOSE)
m_sts_line = p_sts_line.match(msg_lines)
if m_sts_line is not None:
sts_line_dict = m_sts_line.groupdict()
return sts_line_dict, msg_lines[m_sts_line.end():]
def get_msg_body(msg_lines):
'''Return a message body'''
end = msg_lines.rfind(b'\r\n\r\n')
print(end)
if end is not -1:
return msg_lines[end:]
else:
raise ValueError('msg_lines is a invalid format')
########################################################################
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect(SERVER_ADDRESS)
print_msg('cn', 'The connection accepted')
# Sending the request
http_msg = make_http_msg()
remaining = len(make_http_msg())
res = 0
while remaining > 0:
res = sock.send(http_msg[res:])
remaining -= res
sock.shutdown(socket.SHUT_WR)
# Receiving the response
recd_data = b''
while True:
data = sock.recv(32)
print(data)
if not data:
break
recd_data += data
finally:
sock.close()
print_msg('cl', 'The connection closed.')
status_dict, res_msg = status_line_parser(recd_data)
if status_dict['status_code'].decode() == '200':
html_lines = get_msg_body(res_msg)
html_lines = html_lines.decode()
mybrowser(html_lines)
server.py
server.py
import socket
from datetime import datetime, timezone
from colored_print import print_msg
# Response
SERVER_ADDRESS = ('192.168.1.12', 8000)
RETURN_PHRASE = '\r\n'
VERSION = 'HTTP/1.1'
def make_status_line():
'''Return http_staus-line'''
reason_phrases = {
'200': 'OK',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed'
}
status_line = \
VERSION + ' ' + '200' + ' ' + reason_phrases['200'] + RETURN_PHRASE
return status_line
def make_header_section(content_length):
'''Return http_header-section'''
field_values = {
'Server': 'Suzukaze Server 1.0',
'Date': datetime.now(timezone.utc).strftime(r'%a, %d %b %Y %X %Z'),
'Connection': 'close',
'Content-Type': 'html/plain',
'Content-Length': content_length
}
header_sc = 'Date:' + field_values['Date'] + RETURN_PHRASE
header_sc += 'Server:' + field_values['Server'] + RETURN_PHRASE
header_sc += 'Connection:' + field_values['Connection'] + RETURN_PHRASE
header_sc += 'Content-Type:' + field_values['Content-Type'] + RETURN_PHRASE
header_sc += 'Content-Length:' + field_values['Content-Length'] + RETURN_PHRASE
header_sc += RETURN_PHRASE
return header_sc
def read_index_html():
'''Return index.html and that length'''
with open('index.html', 'rb') as f:
index_html = f.read()
index_len = len(index_html)
return index_html, index_len
def make_http_msg():
status_line = make_status_line()
index_html, index_len = read_index_html()
msg_body = index_html
content_length = str(index_len)
header_section = make_header_section(content_length)
http_msg = \
bytes(status_line, 'utf-8') + bytes(header_section, 'utf-8') + msg_body
return http_msg
###############################################################
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(SERVER_ADDRESS)
sock.listen(0)
try:
conn_sock, client_address = sock.accept()
print_msg('ac', 'The connection accepted.')
print_msg('i', '{}:{} --------> {}:{}'
.format(client_address[0], client_address[1],
SERVER_ADDRESS[0], SERVER_ADDRESS[1]))
# Receiving the request
recd_data = b''
while True:
data = conn_sock.recv(32)
if not data:
break
recd_data += data
print_msg('i', 'Total received: {}'.format(recd_data))
# Sending the response
http_msg = make_http_msg()
remaining = len(http_msg)
res = 0
while remaining > 0:
res = conn_sock.send(http_msg[res:])
remaining -= res
finally:
conn_sock.shutdown(socket.SHUT_WR)
conn_sock.close()
print_msg('cl', 'The connection closed.')
colored_print.py
colored_print.py
from colorama import Fore, Style
def print_msg(header, msg):
'''header are i that is INFO or e that is ERROR'''
if header == 'i':
print(Fore.GREEN + '[INFO]',
Style.RESET_ALL + msg)
elif header == 'e':
print(Fore.RED + '[ERROR]',
Style.RESET_ALL + msg)
elif header == 'ac':
print(Fore.BLUE + '[ACCEPT]',
Style.RESET_ALL + msg)
elif header == 'cn':
print(Fore.BLUE + '[CONNECT]',
Style.RESET_ALL + msg)
elif header == 'cl':
print(Fore.BLUE + '[CLOSE]',
Style.RESET_ALL + msg)
else:
print(Fore.RED + 'ERROR: header is an invalid value.'
+ Style.RESET_ALL)
browser_app.py
browser_app.py
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView
class Ui_dialog(object):
def setupUi(self, dialog, html_source):
dialog.setObjectName('dialog')
dialog.resize(1000, 650)
self.stackedWidget = QtWidgets.QStackedWidget(dialog)
self.stackedWidget.setGeometry(QtCore.QRect(10, 50, 980, 590))
self.stackedWidget.setObjectName('stackedWidget')
self.page_1 = QtWidgets.QWidget()
self.page_1.setObjectName('page_1')
self.webView = QWebView(self.page_1)
self.webView.setGeometry(QtCore.QRect(0, 0, 980, 590))
self.webView.setObjectName('webView')
self.stackedWidget.addWidget(self.page_1)
self.page_2 = QtWidgets.QWidget()
self.page_2.setObjectName('page_2')
self.page_2_title = QtWidgets.QLabel(self.page_2)
self.page_2_title.setGeometry(QtCore.QRect(370, 60, 240, 50))
font = QtGui.QFont()
font.setPointSize(24)
self.page_2_title.setFont(font)
self.page_2_title.setObjectName('page_2_title')
self.page_2_label1 = QtWidgets.QLabel(self.page_2)
self.page_2_label1.setGeometry(QtCore.QRect(435, 230, 110, 22))
font = QtGui.QFont()
font.setPointSize(16)
self.page_2_label1.setFont(font)
self.page_2_label1.setObjectName('page_2_label1')
self.page_2_ip = QtWidgets.QLineEdit(self.page_2)
self.page_2_ip.setGeometry(QtCore.QRect(435, 262, 110, 28))
font = QtGui.QFont()
font.setPointSize(12)
self.page_2_ip.setFont(font)
self.page_2_ip.setObjectName('page_2_ip')
self.page_2_label2 = QtWidgets.QLabel(self.page_2)
self.page_2_label2.setGeometry(QtCore.QRect(468, 310, 42, 16))
font = QtGui.QFont()
font.setPointSize(16)
self.page_2_label2.setFont(font)
self.page_2_label2.setObjectName('page_2_label2')
self.page_2_url = QtWidgets.QLineEdit(self.page_2)
self.page_2_url.setGeometry(QtCore.QRect(335, 336, 310, 28))
font = QtGui.QFont()
font.setPointSize(12)
self.page_2_url.setFont(font)
self.page_2_url.setObjectName('page_2_url')
self.page_2_save = QtWidgets.QPushButton(self.page_2)
self.page_2_save.setGeometry(QtCore.QRect(425, 384, 130, 40))
font = QtGui.QFont()
font.setPointSize(18)
self.page_2_save.setFont(font)
self.page_2_save.setObjectName('page_2_save')
self.stackedWidget.addWidget(self.page_2)
self.url_lineEdit = QtWidgets.QLineEdit(dialog)
self.url_lineEdit.setGeometry(QtCore.QRect(10, 10, 666, 30))
font = QtGui.QFont()
font.setPointSize(12)
self.url_lineEdit.setFont(font)
self.url_lineEdit.setObjectName('url_lineEdit')
self.search_btn = QtWidgets.QPushButton(dialog)
self.search_btn.setGeometry(QtCore.QRect(670, 10, 90, 30))
font = QtGui.QFont()
font.setPointSize(12)
self.search_btn.setFont(font)
self.search_btn.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
self.search_btn.setObjectName('search_btn')
self.config_btn = QtWidgets.QPushButton(dialog)
self.config_btn.setGeometry(QtCore.QRect(780, 10, 60, 30))
font = QtGui.QFont()
font.setPointSize(10)
self.config_btn.setFont(font)
self.config_btn.setObjectName('config_btn')
# ----------------------------------------------
self.url_lineEdit.setText('in preparation')
self.page_2_title.setText('Setting the DNS')
self.page_2_label1.setText('IP Address')
self.page_2_ip.setText('in preparation')
self.page_2_label2.setText('URL')
self.page_2_url.setText('in preparation')
self.webView.setHtml(html_source)
# ----------------------------------------------
QtCore.QMetaObject.connectSlotsByName(dialog)
def mybrowser(html_source):
app = QtWidgets.QApplication(sys.argv)
dialog = QtWidgets.QDialog()
ui = Ui_dialog()
ui.setupUi(dialog, html_source)
dialog.show()
sys.exit(app.exec_())
Recommended Posts