Socketモジュールを使ったHTTP通信
python3のsocketモジュールを使った、HTTP通信の簡単な例
HTTPは簡単に言うと、HTMLファイルをTCP通信(TCPとは限らない)で送ったりするために、本来はどんなデータでも良いTCPのデータの部分(TCPペイロードとか言う)に対してルール(データはこのフォーマットに従ってくださいという)を作ったわけだから、recv,sendでそのフォーマットに従ったデータを送れば良い。
ここでは、RFC7230,RFC7230日本語訳を読みながら進める。
※HTTP通信とは「HTTP」と呼ばれるお約束事に従って行われる通信のこと。だそうです。
##スタートライン(start-line)とヘッダ節(header-section)
スタートラインについて start-line
HTTPメッセージはクライアントからサーバーへのリクエストとサーバーからクライアントへのレスポンスのいずれかになる。このスタートラインでこの2つを判別することができる。スタートラインは前者の場合リクエストライン、後者の場合ステータスラインというフォーマットを取る。
###ヘッダ節について header-section
今回は次のヘッダフィールドを使う。
Serverサイド
- Server
- Connection
- Content-Type
- Content-Length
####Clientサイド
- Accept
- Host
- Connection
- User-Agent
実際のところ今回はどのヘッダフィールドの値も使いませんでした。
ここまでの実装
ここではコードやヘッダ節の値を固定しているが、実際には知っての通り動的に決められます。
クライアント側のリクエストライン+ヘッダ節
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)
関数make_request_line(メソッド, リクエストターゲット) -> リクエストライン
は次を返す。
GET / HTTP/1.1
関数make_header_section() -> ヘッダ節
は次を返す。
Host:192.168.1.200
Accept:html/plain
Connection:close
User-Agent:Suzukaze Browser 1.0
空行
サーバー側のステータスライン+ヘッダ節
コードは200固定。だからreason_phrases
は余計だが雰囲気のために追加。
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() -> ステータスライン
は次を返す。
HTTP/1.1 200 OK
#####関数make_header_section(Content-Lengthの値) -> ヘッダ節
は次を返す。
Date:Mon, 00 Nov 2020 00:00:00 UTC
Server:Suzukaze Server 1.0
Connection:close
Content-Type:html/plain
Content-Length:1224
空行
#####関数read_index_html() -> index.htmlの中身, index.htmlの長さ
は次を返す。
b"<!DOCTYPE html>\n<head>\n <meta charset='utf-8'>\n</head>\n<style>\n*
(略)
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
サーバー側がリクエストから取り出す必要がある情報
今回のコードではリクエストされるのが'/'であることがわかっているので、request-targetは必要ないし、ConnectionもCloseするから結局のところサーバーが必要な情報はありません。だからリクエストラインとヘッダ節はスルーします。
##メッセージボディ(message-body)
サーバー側のメッセージボディを実装すると言ってもすでにindex.htmlをバイト列として読み込んだのでサーバー側のヘッダ節の後ろにくっつけるだけである。クライアント側のメッセージボディは無い。
ここで注意しないといけないのがスタートラインとヘッダ節がUnicode文字列(str型)であることである。ソケットで送信するにはバイト列(byte-like-object)にする必要があるので変換する。
クライアント側
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
ソケット通信の実装
後はこのHTTPメッセージを送り合うだけです。
ここのコードは、Socketモジュールを使ったTCP通信 - 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.')
クライアント側
リクエストを送り終えたらshutdownすることでサーバー側にこれ以上こちらはデータを送らないと宣言できる。(ソケットレベルでは空バイトが送られる。だからサーバー側は空バイトの受信でリクエストの終端を検出できる。)
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.')
データを取り出す
####クライアント側
とりあえずクライアント側は次の2つを取り出す必要がある。
-
ステータスラインの情報。これは次の正規表現で辞書として取り出す。
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
を指定する必要がある。そうすることで文字クラス[]
内にない空白は無視される。次のように取り出される。
{'version': b'HTTP/1.1', 'status_code': b'200', 'reason': b'OK'}
-
htmlのコードを含んだメッセージボディ。ヘッダ節の終わりは空行で表すルールなので、
b'\r\n\r\n'
と一致する部分の最後のインデックスを取得して、そのインデックスを[start:end]
のstartにして新しい(メッセージボディだけの)バイト列を生成する。そしてそれをデコードすると元のhtmlのコードが得られます。つまりサーバーサイドの下記文字列(html_source)と等しい。with open('sample.html', 'r') as f: html_source = f.read()
サーバー側
今の所サーバー側が必要な情報は無い。
得たhtmlをブラウザもどきで表示させる
ここで簡単なhtmlビュワーをPyqt5をつかって作る。UIはデザイナーを使って作り、それをPyQt5 UI code generator 5.15.1
でPython3コードに変換してそのコードをいじって使う。本来はUIを書き換えても問題ないようにユーザーが追加するロジックは別に作ったクラスに書いていくが今回はUIを書き換えないので、そのまま設定していく。ブラウザは次のようなコードとなった。このモジュールをソケット通信の行うメインのモジュールでインポートする。
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('準備中')
self.page_2_title.setText('Setting the DNS')
self.page_2_label1.setText('IP Address')
self.page_2_ip.setText('準備中')
self.page_2_label2.setText('URL')
self.page_2_url.setText('準備中')
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_())
これを同じディレクトリに置いて、クライアント側でインポートして次のように呼び出す。
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)
そうすると、
このように表示される。
##ソースコード
####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
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
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
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('準備中')
self.page_2_title.setText('Setting the DNS')
self.page_2_label1.setText('IP Address')
self.page_2_ip.setText('準備中')
self.page_2_label2.setText('URL')
self.page_2_url.setText('準備中')
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_())