├── screenshot.png
├── .gitignore
├── Makefile
├── LICENSE
├── setup.py
├── httpstat_test.sh
├── README.md
└── httpstat.py
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/reorx/httpstat/HEAD/screenshot.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac #
2 | .DS_Store
3 |
4 | # Vim swap files #
5 | *.sw[po]
6 |
7 | # Byte-compiled
8 | *.py[cod]
9 |
10 | # Distribution / packaging
11 | /build/
12 | /dist/
13 | *.egg-info/
14 |
15 | # Sphinx documentation
16 | docs/_build/
17 |
18 | # Others #
19 | .virtualenv
20 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: test build
2 |
3 | test:
4 | @bash httpstat_test.sh
5 |
6 | clean:
7 | rm -rf build dist *.egg-info
8 |
9 | build:
10 | python setup.py build
11 |
12 | build-dist:
13 | python setup.py sdist bdist_wheel
14 |
15 | publish: clean build-dist
16 | python -m twine upload dist/*
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Xiao Meng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 |
4 | from setuptools import setup
5 |
6 |
7 | package_name = 'httpstat'
8 | filename = package_name + '.py'
9 |
10 |
11 | def get_version():
12 | import ast
13 |
14 | with open(filename) as input_file:
15 | for line in input_file:
16 | if line.startswith('__version__'):
17 | return ast.parse(line).body[0].value.s
18 |
19 |
20 | def get_long_description():
21 | try:
22 | with open('README.md', 'r') as f:
23 | return f.read()
24 | except IOError:
25 | return ''
26 |
27 |
28 | setup(
29 | name=package_name,
30 | version=get_version(),
31 | author='reorx',
32 | author_email='novoreorx@gmail.com',
33 | description='curl statistics made simple',
34 | url='https://github.com/reorx/httpstat',
35 | long_description=get_long_description(),
36 | long_description_content_type='text/markdown',
37 | py_modules=[package_name],
38 | entry_points={
39 | 'console_scripts': [
40 | 'httpstat = httpstat:main'
41 | ]
42 | },
43 | license='License :: OSI Approved :: MIT License',
44 | )
45 |
--------------------------------------------------------------------------------
/httpstat_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | function assert_exit() {
4 | rc=$?
5 | expect=$1
6 | if [ "$rc" -eq "$expect" ]; then
7 | echo OK
8 | else
9 | echo "Failed, expect $expect, got $rc"
10 | exit 1
11 | fi
12 | }
13 |
14 | function title() {
15 | echo
16 | echo "Test $1 ..."
17 | }
18 |
19 | function check_url() {
20 | url=$1
21 | echo "Checking $url ..."
22 | if curl -s --head "$url" >/dev/null; then
23 | echo "URL $url is accessible"
24 | else
25 | echo "URL $url is not accessible"
26 | exit 1
27 | fi
28 | }
29 |
30 | http_url="www.gstatic.com/generate_204"
31 | https_url="https://http2.akamai.com"
32 |
33 | check_url "$http_url"
34 | check_url "$https_url"
35 |
36 | for pybin in python python3; do
37 | #for pybin in python; do
38 | echo
39 | echo "# Test in $pybin"
40 |
41 | function main() {
42 | $pybin httpstat.py $@ 2>&1
43 | }
44 |
45 | function main_silent() {
46 | $pybin httpstat.py $@ >/dev/null 2>&1
47 | }
48 |
49 | title "basic"
50 | main_silent $http_url
51 | assert_exit 0
52 |
53 | title "https site ($https_url)"
54 | main_silent $https_url
55 | assert_exit 0
56 |
57 | title "comma decimal language (ru_RU)"
58 | LC_ALL=ru_RU main_silent $http_url
59 | assert_exit 0
60 |
61 | title "HTTPSTAT_DEBUG"
62 | HTTPSTAT_DEBUG=true main $http_url | grep -q 'HTTPSTAT_DEBUG=true'
63 | assert_exit 0
64 |
65 | title "HTTPSTAT_SHOW_SPEED"
66 | HTTPSTAT_SHOW_SPEED=true main $http_url | grep -q 'speed_download'
67 | assert_exit 0
68 |
69 | title "HTTPSTAT_CURL_BIN"
70 | HTTPSTAT_CURL_BIN=/usr/bin/curl HTTPSTAT_DEBUG=true main $http_url | grep -q '/usr/bin/curl'
71 | assert_exit 0
72 |
73 | title "HTTPSTAT_SHOW_IP"
74 | HTTPSTAT_SHOW_IP="true" main $http_url | grep -q 'Connected'
75 | assert_exit 0
76 |
77 | title "HTTPSTAT_SHOW_BODY=true, -G --data-urlencode \"a=中文\""
78 | HTTPSTAT_SHOW_BODY="true" main_silent httpbin.org/get -G --data-urlencode "a=中文"
79 | assert_exit 0
80 |
81 | title "HTTPSTAT_SHOW_BODY=true, -G --data-urlencode \"a=中文\""
82 | HTTPSTAT_SHOW_BODY="true" main_silent httpbin.org/post -X POST --data-urlencode "a=中文"
83 | assert_exit 0
84 |
85 | title "HTTPSTAT_SAVE_BODY=true"
86 | HTTPSTAT_SAVE_BODY=true main $http_url | grep -q 'stored in'
87 | assert_exit 0
88 |
89 | title "HTTPSTAT_SAVE_BODY=false"
90 | HTTPSTAT_SAVE_BODY=false HTTPSTAT_DEBUG=true main $http_url | grep -q 'rm body file'
91 | assert_exit 0
92 |
93 | title "HTTPSTAT_SHOW_BODY=true HTTPSTAT_SAVE_BODY=true, has 'is truncated, has 'stored in'"
94 | out=$(HTTPSTAT_SHOW_BODY=true HTTPSTAT_SAVE_BODY=true \
95 | main $https_url)
96 | echo "$out" | grep -q 'is truncated'
97 | assert_exit 0
98 |
99 | echo "$out" | grep -q 'stored in'
100 | assert_exit 0
101 |
102 | title "HTTPSTAT_SHOW_BODY=true HTTPSTAT_SAVE_BODY=false, has 'is truncated', no 'stored in'"
103 | out=$(HTTPSTAT_SHOW_BODY=true HTTPSTAT_SAVE_BODY=false \
104 | main $https_url)
105 | echo "$out" | grep -q 'is truncated'
106 | assert_exit 0
107 |
108 | echo "$out" | grep -q 'stored in'
109 | assert_exit 1
110 | done
111 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # httpstat
2 |
3 | 
4 |
5 | httpstat visualizes `curl(1)` statistics in a way of beauty and clarity.
6 |
7 | It is a **single file🌟** Python script that has **no dependency👏** and is compatible with **Python 3🍻**.
8 |
9 |
10 | ## Installation
11 |
12 | There are three ways to get `httpstat`:
13 |
14 | - Download the script directly: `wget https://raw.githubusercontent.com/reorx/httpstat/master/httpstat.py`
15 |
16 | - Through pip: `pip install httpstat`
17 |
18 | - Through homebrew (macOS only): `brew install httpstat`
19 |
20 | > For Windows users, @davecheney's [Go version](https://github.com/davecheney/httpstat) is suggested. → [download link](https://github.com/davecheney/httpstat/releases)
21 |
22 | ## Usage
23 |
24 | Simply:
25 |
26 | ```bash
27 | python httpstat.py httpbin.org/get
28 | ```
29 |
30 | If installed through pip or brew, you can use `httpstat` as a command:
31 |
32 | ```bash
33 | httpstat httpbin.org/get
34 | ```
35 |
36 | ### cURL Options
37 |
38 | Because `httpstat` is a wrapper of cURL, you can pass any cURL supported option after the url (except for `-w`, `-D`, `-o`, `-s`, `-S` which are already used by `httpstat`):
39 |
40 | ```bash
41 | httpstat httpbin.org/post -X POST --data-urlencode "a=b" -v
42 | ```
43 |
44 | ### Environment Variables
45 |
46 | `httpstat` has a bunch of environment variables to control its behavior.
47 | Here are some usage demos, you can also run `httpstat --help` to see full explanation.
48 |
49 | - HTTPSTAT_SHOW_BODY
50 |
51 | Set to `true` to show response body in the output, note that body length
52 | is limited to 1023 bytes, will be truncated if exceeds. Default is `false`.
53 |
54 | - HTTPSTAT_SHOW_IP
55 |
56 | By default httpstat shows remote and local IP/port address.
57 | Set to `false` to disable this feature. Default is `true`.
58 |
59 | - HTTPSTAT_SHOW_SPEED
60 |
61 | Set to `true` to show download and upload speed. Default is `false`.
62 |
63 | ```bash
64 | HTTPSTAT_SHOW_SPEED=true httpstat http://cachefly.cachefly.net/10mb.test
65 |
66 | ...
67 | speed_download: 3193.3 KiB/s, speed_upload: 0.0 KiB/s
68 | ```
69 |
70 | - HTTPSTAT_SAVE_BODY
71 |
72 | By default httpstat stores body in a tmp file,
73 | set to `false` to disable this feature. Default is `true`
74 |
75 | - HTTPSTAT_CURL_BIN
76 |
77 | Indicate the cURL bin path to use. Default is `curl` from current shell $PATH.
78 |
79 | This exampe uses brew installed cURL to make HTTP2 request:
80 |
81 | ```bash
82 | HTTPSTAT_CURL_BIN=/usr/local/Cellar/curl/7.50.3/bin/curl httpstat https://http2.akamai.com/ --http2
83 |
84 | HTTP/2 200
85 | ...
86 | ```
87 |
88 | > cURL must be compiled with nghttp2 to enable http2 feature
89 | > ([#12](https://github.com/reorx/httpstat/issues/12)).
90 |
91 | - HTTPSTAT_METRICS_ONLY
92 |
93 | If set to `true`, httpstat will only output metrics in json format,
94 | this is useful if you want to parse the data instead of reading it.
95 |
96 | - HTTPSTAT_DEBUG
97 |
98 | Set to `true` to see debugging logs. Default is `false`
99 |
100 |
101 | For convenience, you can export these environments in your `.zshrc` or `.bashrc`,
102 | example:
103 |
104 | ```bash
105 | export HTTPSTAT_SHOW_IP=false
106 | export HTTPSTAT_SHOW_SPEED=true
107 | export HTTPSTAT_SAVE_BODY=false
108 | ```
109 |
110 | ## Related Projects
111 |
112 | Here are some implementations in various languages:
113 |
114 |
115 | - Go: [davecheney/httpstat](https://github.com/davecheney/httpstat)
116 |
117 | This is the Go alternative of httpstat, it's written in pure Go and relies no external programs. Choose it if you like solid binary executions (actually I do).
118 |
119 | - Go (library): [tcnksm/go-httpstat](https://github.com/tcnksm/go-httpstat)
120 |
121 | Other than being a cli tool, this project is used as library to help debugging latency of HTTP requests in Go code, very thoughtful and useful, see more in this [article](https://medium.com/@deeeet/trancing-http-request-latency-in-golang-65b2463f548c#.mm1u8kfnu)
122 |
123 | - Bash: [b4b4r07/httpstat](https://github.com/b4b4r07/httpstat)
124 |
125 | This is what exactly I want to do at the very beginning, but gave up due to not confident in my bash skill, good job!
126 |
127 | - Node: [yosuke-furukawa/httpstat](https://github.com/yosuke-furukawa/httpstat)
128 |
129 | [b4b4r07](https://twitter.com/b4b4r07) mentioned this in his [article](https://tellme.tokyo/post/2016/09/25/213810), could be used as a HTTP client also.
130 |
131 | - PHP: [talhasch/php-httpstat](https://github.com/talhasch/php-httpstat)
132 |
133 | The PHP implementation by @talhasch
134 |
135 | Some code blocks in `httpstat` are copied from other projects of mine, have a look:
136 |
137 | - [reorx/python-terminal-color](https://github.com/reorx/python-terminal-color) Drop-in single file library for printing terminal color.
138 |
139 | - [reorx/getenv](https://github.com/reorx/getenv) Environment variable definition with type.
140 |
--------------------------------------------------------------------------------
/httpstat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding: utf-8
3 | # References:
4 | # man curl
5 | # https://curl.haxx.se/libcurl/c/curl_easy_getinfo.html
6 | # https://curl.haxx.se/libcurl/c/easy_getinfo_options.html
7 | # http://blog.kenweiner.com/2014/11/http-request-timings-with-curl.html
8 |
9 | from __future__ import print_function
10 |
11 | import os
12 | import json
13 | import sys
14 | import logging
15 | import tempfile
16 | import subprocess
17 |
18 |
19 | __version__ = '1.3.2'
20 |
21 |
22 | PY3 = sys.version_info >= (3,)
23 |
24 | if PY3:
25 | xrange = range
26 |
27 |
28 | # Env class is copied from https://github.com/reorx/getenv/blob/master/getenv.py
29 | class Env(object):
30 | prefix = 'HTTPSTAT'
31 | _instances = []
32 |
33 | def __init__(self, key):
34 | self.key = key.format(prefix=self.prefix)
35 | Env._instances.append(self)
36 |
37 | def get(self, default=None):
38 | return os.environ.get(self.key, default)
39 |
40 |
41 | ENV_SHOW_BODY = Env('{prefix}_SHOW_BODY')
42 | ENV_SHOW_IP = Env('{prefix}_SHOW_IP')
43 | ENV_SHOW_SPEED = Env('{prefix}_SHOW_SPEED')
44 | ENV_SAVE_BODY = Env('{prefix}_SAVE_BODY')
45 | ENV_CURL_BIN = Env('{prefix}_CURL_BIN')
46 | ENV_METRICS_ONLY = Env('{prefix}_METRICS_ONLY')
47 | ENV_DEBUG = Env('{prefix}_DEBUG')
48 |
49 |
50 | curl_format = """{
51 | "time_namelookup": %{time_namelookup},
52 | "time_connect": %{time_connect},
53 | "time_appconnect": %{time_appconnect},
54 | "time_pretransfer": %{time_pretransfer},
55 | "time_redirect": %{time_redirect},
56 | "time_starttransfer": %{time_starttransfer},
57 | "time_total": %{time_total},
58 | "speed_download": %{speed_download},
59 | "speed_upload": %{speed_upload},
60 | "remote_ip": "%{remote_ip}",
61 | "remote_port": "%{remote_port}",
62 | "local_ip": "%{local_ip}",
63 | "local_port": "%{local_port}"
64 | }"""
65 |
66 | https_template = """
67 | DNS Lookup TCP Connection TLS Handshake Server Processing Content Transfer
68 | [ {a0000} | {a0001} | {a0002} | {a0003} | {a0004} ]
69 | | | | | |
70 | namelookup:{b0000} | | | |
71 | connect:{b0001} | | |
72 | pretransfer:{b0002} | |
73 | starttransfer:{b0003} |
74 | total:{b0004}
75 | """[1:]
76 |
77 | http_template = """
78 | DNS Lookup TCP Connection Server Processing Content Transfer
79 | [ {a0000} | {a0001} | {a0003} | {a0004} ]
80 | | | | |
81 | namelookup:{b0000} | | |
82 | connect:{b0001} | |
83 | starttransfer:{b0003} |
84 | total:{b0004}
85 | """[1:]
86 |
87 |
88 | # Color code is copied from https://github.com/reorx/python-terminal-color/blob/master/color_simple.py
89 | ISATTY = sys.stdout.isatty()
90 |
91 |
92 | def make_color(code):
93 | def color_func(s):
94 | if not ISATTY:
95 | return s
96 | tpl = '\x1b[{}m{}\x1b[0m'
97 | return tpl.format(code, s)
98 | return color_func
99 |
100 |
101 | red = make_color(31)
102 | green = make_color(32)
103 | yellow = make_color(33)
104 | blue = make_color(34)
105 | magenta = make_color(35)
106 | cyan = make_color(36)
107 |
108 | bold = make_color(1)
109 | underline = make_color(4)
110 |
111 | grayscale = {(i - 232): make_color('38;5;' + str(i)) for i in xrange(232, 256)}
112 |
113 |
114 | def quit(s, code=0):
115 | if s is not None:
116 | print(s)
117 | sys.exit(code)
118 |
119 |
120 | def print_help():
121 | help = """
122 | Usage: httpstat URL [CURL_OPTIONS]
123 | httpstat -h | --help
124 | httpstat --version
125 |
126 | Arguments:
127 | URL url to request, could be with or without `http(s)://` prefix
128 |
129 | Options:
130 | CURL_OPTIONS any curl supported options, except for -w -D -o -S -s,
131 | which are already used internally.
132 | -h --help show this screen.
133 | --version show version.
134 |
135 | Environments:
136 | HTTPSTAT_SHOW_BODY Set to `true` to show response body in the output,
137 | note that body length is limited to 1023 bytes, will be
138 | truncated if exceeds. Default is `false`.
139 | HTTPSTAT_SHOW_IP By default httpstat shows remote and local IP/port address.
140 | Set to `false` to disable this feature. Default is `true`.
141 | HTTPSTAT_SHOW_SPEED Set to `true` to show download and upload speed.
142 | Default is `false`.
143 | HTTPSTAT_SAVE_BODY By default httpstat stores body in a tmp file,
144 | set to `false` to disable this feature. Default is `true`
145 | HTTPSTAT_CURL_BIN Indicate the curl bin path to use. Default is `curl`
146 | from current shell $PATH.
147 | HTTPSTAT_DEBUG Set to `true` to see debugging logs. Default is `false`
148 | """[1:-1]
149 | print(help)
150 |
151 |
152 | def main():
153 | args = sys.argv[1:]
154 | if not args:
155 | print_help()
156 | quit(None, 0)
157 |
158 | # get envs
159 | show_body = 'true' in ENV_SHOW_BODY.get('false').lower()
160 | show_ip = 'true' in ENV_SHOW_IP.get('true').lower()
161 | show_speed = 'true'in ENV_SHOW_SPEED.get('false').lower()
162 | save_body = 'true' in ENV_SAVE_BODY.get('true').lower()
163 | curl_bin = ENV_CURL_BIN.get('curl')
164 | metrics_only = 'true' in ENV_METRICS_ONLY.get('false').lower()
165 | is_debug = 'true' in ENV_DEBUG.get('false').lower()
166 |
167 | # configure logging
168 | if is_debug:
169 | log_level = logging.DEBUG
170 | else:
171 | log_level = logging.INFO
172 | logging.basicConfig(level=log_level)
173 | lg = logging.getLogger('httpstat')
174 |
175 | # log envs
176 | lg.debug('Envs:\n%s', '\n'.join(' {}={}'.format(i.key, i.get('')) for i in Env._instances))
177 | lg.debug('Flags: %s', dict(
178 | show_body=show_body,
179 | show_ip=show_ip,
180 | show_speed=show_speed,
181 | save_body=save_body,
182 | curl_bin=curl_bin,
183 | is_debug=is_debug,
184 | ))
185 |
186 | # get url
187 | url = args[0]
188 | if url in ['-h', '--help']:
189 | print_help()
190 | quit(None, 0)
191 | elif url == '--version':
192 | print('httpstat {}'.format(__version__))
193 | quit(None, 0)
194 |
195 | curl_args = args[1:]
196 |
197 | # check curl args
198 | exclude_options = [
199 | '-w', '--write-out',
200 | '-D', '--dump-header',
201 | '-o', '--output',
202 | '-s', '--silent',
203 | ]
204 | for i in exclude_options:
205 | if i in curl_args:
206 | quit(yellow('Error: {} is not allowed in extra curl args'.format(i)), 1)
207 |
208 | # tempfile for output
209 | bodyf = tempfile.NamedTemporaryFile(delete=False)
210 | bodyf.close()
211 |
212 | headerf = tempfile.NamedTemporaryFile(delete=False)
213 | headerf.close()
214 |
215 | # run cmd
216 | cmd_env = os.environ.copy()
217 | cmd_env.update(
218 | LC_ALL='C',
219 | )
220 | cmd_core = [curl_bin, '-w', curl_format, '-D', headerf.name, '-o', bodyf.name, '-s', '-S']
221 | cmd = cmd_core + curl_args + [url]
222 | lg.debug('cmd: %s', cmd)
223 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=cmd_env)
224 | out, err = p.communicate()
225 | if PY3:
226 | out, err = out.decode(), err.decode()
227 | lg.debug('out: %s', out)
228 |
229 | # print stderr
230 | if p.returncode == 0:
231 | if err:
232 | print(grayscale[16](err))
233 | else:
234 | _cmd = list(cmd)
235 | _cmd[2] = ''
236 | _cmd[4] = ''
237 | _cmd[6] = ''
238 | print('> {}'.format(' '.join(_cmd)))
239 | quit(yellow('curl error: {}'.format(err)), p.returncode)
240 |
241 | # parse output
242 | try:
243 | d = json.loads(out)
244 | except ValueError as e:
245 | print(yellow('Could not decode json: {}'.format(e)))
246 | print('curl result:', p.returncode, grayscale[16](out), grayscale[16](err))
247 | quit(None, 1)
248 |
249 | # convert time_ metrics from seconds to milliseconds
250 | for k in d:
251 | if k.startswith('time_'):
252 | v = d[k]
253 | # Convert time_ values to milliseconds in int
254 | if isinstance(v, float):
255 | # Before 7.61.0, time values are represented as seconds in float
256 | d[k] = int(v * 1000)
257 | elif isinstance(v, int):
258 | # Starting from 7.61.0, libcurl uses microsecond in int
259 | # to return time values, references:
260 | # https://daniel.haxx.se/blog/2018/07/11/curl-7-61-0/
261 | # https://curl.se/bug/?i=2495
262 | d[k] = int(v / 1000)
263 | else:
264 | raise TypeError('{} value type is invalid: {}'.format(k, type(v)))
265 |
266 | # calculate ranges
267 | d.update(
268 | range_dns=d['time_namelookup'],
269 | range_connection=d['time_connect'] - d['time_namelookup'],
270 | range_ssl=d['time_pretransfer'] - d['time_connect'],
271 | range_server=d['time_starttransfer'] - d['time_pretransfer'],
272 | range_transfer=d['time_total'] - d['time_starttransfer'],
273 | )
274 |
275 | # print json if metrics_only is enabled
276 | if metrics_only:
277 | print(json.dumps(d, indent=2))
278 | quit(None, 0)
279 |
280 | # ip
281 | if show_ip:
282 | s = 'Connected to {}:{} from {}:{}'.format(
283 | cyan(d['remote_ip']), cyan(d['remote_port']),
284 | d['local_ip'], d['local_port'],
285 | )
286 | print(s)
287 | print()
288 |
289 | # print header & body summary
290 | with open(headerf.name, 'r') as f:
291 | headers = f.read().strip()
292 | # remove header file
293 | lg.debug('rm header file %s', headerf.name)
294 | os.remove(headerf.name)
295 |
296 | for loop, line in enumerate(headers.split('\n')):
297 | if loop == 0:
298 | p1, p2 = tuple(line.split('/'))
299 | print(green(p1) + grayscale[14]('/') + cyan(p2))
300 | else:
301 | pos = line.find(':')
302 | print(grayscale[14](line[:pos + 1]) + cyan(line[pos + 1:]))
303 |
304 | print()
305 |
306 | # body
307 | if show_body:
308 | body_limit = 1024
309 | with open(bodyf.name, 'r') as f:
310 | body = f.read().strip()
311 | body_len = len(body)
312 |
313 | if body_len > body_limit:
314 | print(body[:body_limit] + cyan('...'))
315 | print()
316 | s = '{} is truncated ({} out of {})'.format(green('Body'), body_limit, body_len)
317 | if save_body:
318 | s += ', stored in: {}'.format(bodyf.name)
319 | print(s)
320 | else:
321 | print(body)
322 | else:
323 | if save_body:
324 | print('{} stored in: {}'.format(green('Body'), bodyf.name))
325 |
326 | # remove body file
327 | if not save_body:
328 | lg.debug('rm body file %s', bodyf.name)
329 | os.remove(bodyf.name)
330 |
331 | # print stat
332 | if url.startswith('https://'):
333 | template = https_template
334 | else:
335 | template = http_template
336 |
337 | # colorize template first line
338 | tpl_parts = template.split('\n')
339 | tpl_parts[0] = grayscale[16](tpl_parts[0])
340 | template = '\n'.join(tpl_parts)
341 |
342 | def fmta(s):
343 | return cyan('{:^7}'.format(str(s) + 'ms'))
344 |
345 | def fmtb(s):
346 | return cyan('{:<7}'.format(str(s) + 'ms'))
347 |
348 | stat = template.format(
349 | # a
350 | a0000=fmta(d['range_dns']),
351 | a0001=fmta(d['range_connection']),
352 | a0002=fmta(d['range_ssl']),
353 | a0003=fmta(d['range_server']),
354 | a0004=fmta(d['range_transfer']),
355 | # b
356 | b0000=fmtb(d['time_namelookup']),
357 | b0001=fmtb(d['time_connect']),
358 | b0002=fmtb(d['time_pretransfer']),
359 | b0003=fmtb(d['time_starttransfer']),
360 | b0004=fmtb(d['time_total']),
361 | )
362 | print()
363 | print(stat)
364 |
365 | # speed, originally bytes per second
366 | if show_speed:
367 | print('speed_download: {:.1f} KiB/s, speed_upload: {:.1f} KiB/s'.format(
368 | d['speed_download'] / 1024, d['speed_upload'] / 1024))
369 |
370 |
371 | if __name__ == '__main__':
372 | main()
373 |
--------------------------------------------------------------------------------