From 99008528ced6eda1aec963acc6212111e596964c Mon Sep 17 00:00:00 2001 From: Alexis Delhaie Date: Mon, 24 Aug 2020 19:36:47 +0200 Subject: [PATCH] SSL Support and cleaning code --- .gitignore | 138 ++++++++++++++++++++++++++++++++++++++ build.bat | 3 + src/http_stress_test.py | 123 ---------------------------------- src/main.py | 142 ++++++++++++++++++++++++++++++++++++++++ src/requirements.txt | 1 + src/thread.py | 66 +++++++++++++++++++ 6 files changed, 350 insertions(+), 123 deletions(-) create mode 100644 .gitignore create mode 100644 build.bat delete mode 100644 src/http_stress_test.py create mode 100644 src/main.py create mode 100644 src/requirements.txt create mode 100644 src/thread.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5391d87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..c854f86 --- /dev/null +++ b/build.bat @@ -0,0 +1,3 @@ +@echo off +pip install -r src\requirements.txt +pyinstaller --onefile --clean --console -n=st src\main.py \ No newline at end of file diff --git a/src/http_stress_test.py b/src/http_stress_test.py deleted file mode 100644 index dfbf726..0000000 --- a/src/http_stress_test.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/python3 - -import sys -import http.client -import threading -import time - - -class StressThread (threading.Thread): - def __init__(self, host, port, path, timeout, n): - threading.Thread.__init__(self) - self.host = host - self.port = port - self.path = path - self.timeout = timeout - self.n = n - self.success = False - self.time = 0 - - def run(self): - try: - print("Request {}: GET on {}".format(self.n, self.path)) - now = time.time() - c = http.client.HTTPConnection(self.host, self.port, timeout=self.timeout) - c.request("GET", self.path) - res = c.getresponse() - processed = round((time.time() - now) * 1000) - print("Request {}: {} {} (in {} ms)".format(self.n, res.status, res.reason, processed)) - self.time = processed - if res.status < 400: - self.success = True - except Exception as e: - print("Request {}: {}".format(self.n, e)) - - def is_succeeded(self): - return self.success - - def get_time(self): - return self.time - -def start(host, port, path, timeout, thread_number, one_by_one = False): - thread_array = [] - for i in range(0, thread_number): - thread_array.append(StressThread(host, port, path, timeout, i)) - for t in thread_array: - if one_by_one: - t.run() - else: - t.start() - if not one_by_one: - for t in thread_array: - t.join() - show_stat(thread_array, (timeout * 1000)) - -def show_stat(tArray, timeoutInMs): - total, succeeded = [0, 0] - Tmax, Tmin, Tavg = [0, timeoutInMs, 0] - for t in tArray: - total += 1 - Tavg += t.get_time() - if t.get_time() > Tmax: - Tmax = t.get_time() - if t.get_time() < Tmin: - Tmin = t.get_time() - if t.is_succeeded(): - succeeded += 1 - Tavg = round(Tavg / total) - print("====== FINISHED ======") - print("Failed : {}, Succeeded : {}, Total : {}".format((total - succeeded), succeeded, total)) - print("Min : {} ms, Max : {} ms, Average : {} ms".format(Tmin, Tmax, Tavg)) - -def show_help(): - print("") - print("Usage: python http_stress_test.py -h host [-p port] -pth path [-t number_of_thread] [-tm timeout_in_second]") - print("Exemple: python http_stress_test.py -h www.google.fr -pth / -t 10") - print("") - print("Available arguments:") - print(" -h host The server IP or domain name") - print(" -p port The server HTTP port") - print(" -pth path The path of the HTTP resource") - print(" -t thread Number of threads") - print(" -tm second Timeout of the request") - print(" --one-by-one Send request one by one") - -def get_args(header, important, notfoundcode, valuenotfoundcode, default = ""): - if (header in sys.argv): - try: - i = sys.argv.index(header) - if (len(sys.argv) > (i + 1)): - return sys.argv[i + 1] - elif important: - print ("Error: argument {} found but not the value".format(header)) - show_help() - sys.exit(valuenotfoundcode) - except ValueError: - print ("Error: argument {} not found".format(header)) - if important: - show_help() - sys.exit(notfoundcode) - else: - if important: - print ("Error: argument {} not found".format(header)) - show_help() - sys.exit(notfoundcode) - return default - -def get_flag(header): - return (header in sys.argv) - -if ((len(sys.argv) >= 2) and (("--help" in sys.argv) or ("/?" in sys.argv))): - print("Basic HTTP Stress test") - print("by Alexis Delhaie (@alexlegarnd)") - show_help() - sys.exit(0); - -host = get_args("-h", True, 1001, 1002) -port = int(get_args("-p", False, 1003, 1004, "80")) -path = get_args("-pth", True, 1005, 1006) -thread_number = int(get_args("-t", False, 1007, 1008, "5")) -timeout = int(get_args("-tm", False, 1009, 1010, "10")) -one_by_one = get_flag("--one-by-one") - -start(host, port, path, timeout, thread_number, one_by_one) \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..ee83229 --- /dev/null +++ b/src/main.py @@ -0,0 +1,142 @@ +#!/usr/bin/python3 + +import sys +import time +import platform +import os +from rich import box +from rich.console import Console +from rich.table import Table +from thread import StressThread + +console = Console(highlight=False) +VERSION = "1.2" + +def main(): + if ((len(sys.argv) >= 2) and (("--help" in sys.argv) or ("/?" in sys.argv))): + show_author() + show_help() + return + elif ((len(sys.argv) >= 2) and ("--version" in sys.argv)): + show_version() + return + + host = get_args("-h", True, 1001, 1002) + allow_ssl = get_flag("--ssl") + port = int(get_args("--port", False, 1003, 1004, "80")) + path = get_args("-p", True, 1005, 1006) + thread_number = int(get_args("-t", False, 1007, 1008, "5")) + timeout = int(get_args("-tm", False, 1009, 1010, "10")) + one_by_one = get_flag("--one-by-one") + + self_signed = get_flag("--allow-self-signed") + + start(host, port, path, timeout, thread_number, allow_ssl, self_signed, one_by_one) + +def start(host, port, path, timeout, thread_number, allow_ssl, self_signed, one_by_one = False): + thread_array = [] + for i in range(0, thread_number): + thread_array.append(StressThread(host, port, path, timeout, i, allow_ssl, self_signed, VERSION)) + for t in thread_array: + if one_by_one: + t.run() + else: + try: + t.start() + except: + b = False + while not b: + try: + t.start() + b = True + except: + time.sleep(1) + if not one_by_one: + for t in thread_array: + t.join() + show_stat(thread_array, (timeout * 1000)) + +def show_stat(tArray, timeoutInMs): + total, succeeded = [0, 0] + Tmax, Tmin, Tavg = [0, timeoutInMs, 0] + for t in tArray: + total += 1 + Tavg += t.get_time() + if t.get_time() > Tmax: + Tmax = t.get_time() + if t.get_time() < Tmin: + Tmin = t.get_time() + if t.is_succeeded(): + succeeded += 1 + Tavg = round(Tavg / total) + tresult = Table(title="Result", box=box.ASCII) + tresult.add_column("Failed", style="red") + tresult.add_column("Succeeded", style="green") + tresult.add_column("Total", style="cyan") + tresult.add_row(str(total - succeeded), str(succeeded), str(total)) + # Min, Max and Average Time + ttime = Table(box=box.ASCII) + ttime.add_column("Minimum", style="green") + ttime.add_column("Maximum", style="red") + ttime.add_column("Average", style="cyan") + ttime.add_row(str(Tmin), str(Tmax), str(Tavg)) + console.print(tresult) + console.print(ttime) + +def show_help(): + console.print("") + console.print("Usage: [bold] -h host -p path [--port port] [-t number_of_thread] [-tm timeout_in_second] [--ssl [--allow-self-signed]][/]") + console.print("Exemple: st.exe -h www.google.fr -p / -t 10") + console.print(" python main.py -h www.google.fr -p / -t 10") + console.print("") + console.print("Available arguments:") + console.print(" -h host The server IP or domain name") + console.print(" -p path The path of the HTTP resource") + console.print(" --port port The server HTTP port") + console.print(" -t thread Number of threads") + console.print(" -tm second Timeout of the request") + console.print(" --one-by-one Send request one by one") + console.print(" --ssl Use HTTPS/SSL") + console.print(" --allow-self-signed Allow self signed SSL certificate") + console.print(" --help") + console.print(" /? Show this page") + console.print(" --version Get information about the application and the system") + +def show_author(): + console.print("[bold]Basic HTTP Stress test[/]") + console.print("by Alexis Delhaie ([bold blue]@alexlegarnd[/])") + +def show_version(): + show_author() + console.print() + console.print("Version: [bold]{}[/]".format(VERSION)) + console.print("Python version: [bold]{}[/]".format(sys.version)) + console.print("User-Agent: [bold]PyStressTest/{}({} {} {})[/]".format(VERSION, platform.system(), os.name, platform.release())) + +def get_args(header, important, notfoundcode, valuenotfoundcode, default = ""): + if (header in sys.argv): + try: + i = sys.argv.index(header) + if (len(sys.argv) > (i + 1)): + return sys.argv[i + 1] + elif important: + console.print("[red]Error[/]: argument {} found but not the value".format(header), style="bold") + show_help() + sys.exit(valuenotfoundcode) + except ValueError: + console.print("[red]Error[/]: argument {} not found".format(header), style="bold") + if important: + show_help() + sys.exit(notfoundcode) + else: + if important: + console.print("[red]Error[/]: argument {} not found".format(header), style="bold") + show_help() + sys.exit(notfoundcode) + return default + +def get_flag(header): + return (header in sys.argv) + + +main() \ No newline at end of file diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..c94be38 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1 @@ +rich \ No newline at end of file diff --git a/src/thread.py b/src/thread.py new file mode 100644 index 0000000..54b42c5 --- /dev/null +++ b/src/thread.py @@ -0,0 +1,66 @@ +import threading +import http.client +import platform +import time +import os +import ssl +import encodings.idna +from rich.console import Console + +console = Console(highlight=False) + +class StressThread (threading.Thread): + + def __init__(self, host, port, path, timeout, n, allow_ssl, self_signed, version): + threading.Thread.__init__(self) + self.user_agent = "PyStressTest/{}({} {} {})".format(version, platform.system(), os.name, platform.release()) + self.host = host + self.port = port + self.path = path + self.timeout = timeout + self.n = n + self.allow_ssl = allow_ssl + self.self_signed = self_signed + self.success = False + self.time = 0 + + def run(self): + try: + print("Request {}: GET on {}".format(self.n, self.path)) + now = time.time() + if self.allow_ssl: + if self.self_signed: + c = http.client.HTTPSConnection(self.host, self.port, timeout=self.timeout, key_file=None, cert_file=None, context=ssl._create_unverified_context()) + else: + c = http.client.HTTPSConnection(self.host, self.port, timeout=self.timeout, key_file=None, cert_file=None) + else: + c = http.client.HTTPConnection(self.host, self.port, timeout=self.timeout) + headers = {"User-Agent": self.user_agent} + c.request(method="GET", url=self.path, headers=headers) + res = c.getresponse() + processed = round((time.time() - now) * 1000) + self.print_result(res.status, res.reason, processed) + self.time = processed + if res.status < 400: + self.success = True + except Exception as e: + console.print("Request {}: [bold red]{}[/]".format(self.n, e)) + print() + + def is_succeeded(self): + return self.success + + def get_time(self): + return self.time + + def print_result(self, code, reason, processed): + if code >= 100 and code < 200: + console.print("Request {}: [cyan]{} {}[/] (in [blue]{} ms[/])".format(self.n, code, reason, processed)) + elif code >= 200 and code < 300: + console.print("Request {}: [green]{} {}[/] (in [blue]{} ms[/])".format(self.n, code, reason, processed)) + elif code >= 300 and code < 400: + console.print("Request {}: [cyan]{} {}[/] (in [blue]{} ms[/])".format(self.n, code, reason, processed)) + elif code >= 400 and code < 500: + console.print("Request {}: [orange]{} {}[/] (in [blue]{} ms[/])".format(self.n, code, reason, processed)) + else: + console.print("Request {}: [red]{} {}[/] (in [blue]{} ms[/])".format(self.n, code, reason, processed))