1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
#!python
import sys
sys.path.append("../..")
import os.path
import subprocess
from h11._events import *
from h11._state import *
from h11._state import (
_SWITCH_UPGRADE, _SWITCH_CONNECT,
EVENT_TRIGGERED_TRANSITIONS, STATE_TRIGGERED_TRANSITIONS,
)
_EVENT_COLOR = "#002092"
_STATE_COLOR = "#017517"
_SPECIAL_COLOR = "#7600a1"
HEADER = """
digraph {
graph [fontname = "Lato" bgcolor="transparent"]
node [fontname = "Lato"]
edge [fontname = "Lato"]
"""
def finish(machine_name):
return ("""
labelloc="t"
labeljust="l"
label=<<FONT POINT-SIZE="20">h11 state machine: {}</FONT>>
}}
""".format(machine_name))
class Edges:
def __init__(self):
self.edges = []
def e(self, source, target, label, color, italicize=False, weight=1):
if italicize:
quoted_label = "<<i>{}</i>>".format(label)
else:
quoted_label = '<{}>'.format(label)
self.edges.append(
'{source} -> {target} [\n'
' label={quoted_label},\n'
' color="{color}", fontcolor="{color}",\n'
' weight={weight},\n'
']\n'
.format(**locals()))
def write(self, f):
self.edges.sort()
f.write("".join(self.edges))
def make_dot_special_state(out_path):
with open(out_path, "w") as f:
f.write(HEADER)
f.write("""
kaT [label=<<i>keep-alive is enabled<br/>initial state</i>>]
kaF [label=<<i>keep-alive is disabled</i>>]
upF [label=<<i>No potential Upgrade: pending<br/>initial state</i>>]
upT [label=<<i>Potential Upgrade: pending</i>>]
coF [label=<<i>No potential CONNECT pending<br/>initial state</i>>]
coT [label=<<i>Potential CONNECT pending</i>>]
""")
edges = Edges()
for s in ["kaT", "kaF"]:
edges.e(s, "kaF",
"Request/response with<br/>HTTP/1.0 or Connection: close",
color=_EVENT_COLOR,
italicize=True)
edges.e("upF", "upT",
"Request with Upgrade:",
color=_EVENT_COLOR, italicize=True)
edges.e("upT", "upF",
"Response",
color=_EVENT_COLOR, italicize=True)
edges.e("coF", "coT",
"Request with CONNECT",
color=_EVENT_COLOR, italicize=True)
edges.e("coT", "coF",
"Response without 2xx status",
color=_EVENT_COLOR, italicize=True)
edges.write(f)
f.write(finish("special states"))
def make_dot(role, out_path):
with open(out_path, "w") as f:
f.write(HEADER)
f.write("""
IDLE [label=<IDLE<BR/><i>start state</i>>]
// move ERROR down to the bottom
{rank=same CLOSED ERROR}
""")
# Dot output is sensitive to the order in which the nodes and edges
# are listed. We generate them in python's randomized dict iteration
# order. So to normalize order, we accumulate and then sort.
# Fortunately, this order happens to be one that produces a nice
# layout... with other orders I've seen really terrible layouts, and
# had to do things like move the server's IDLE->MUST_CLOSE to the top
# of the file to fix them.
edges = Edges()
CORE_EVENTS = {Request, InformationalResponse,
Response, Data, EndOfMessage}
for (source_state, t) in EVENT_TRIGGERED_TRANSITIONS[role].items():
for (event_type, target_state) in t.items():
weight = 1
color = _EVENT_COLOR
italicize = False
if (event_type in CORE_EVENTS
and source_state is not target_state):
weight = 10
# exception
if (event_type is Response and source_state is IDLE):
weight = 1
if isinstance(event_type, tuple):
# The weird special cases
#color = _SPECIAL_COLOR
if event_type == (Request, CLIENT):
name = "<i>client makes Request</i>"
weight = 10
elif event_type[1] is _SWITCH_UPGRADE:
name = "<i>101 Switching Protocols</i>"
weight = 1
elif event_type[1] is _SWITCH_CONNECT:
name = "<i>CONNECT accepted</i>"
weight = 1
else:
assert False
else:
name = event_type.__name__
edges.e(source_state, target_state, name, color,
weight=weight, italicize=italicize)
for state_pair, updates in STATE_TRIGGERED_TRANSITIONS.items():
if role not in updates:
continue
if role is CLIENT:
(our_state, their_state) = state_pair
else:
(their_state, our_state) = state_pair
edges.e(our_state, updates[role],
"<i>peer in</i><BR/>{}".format(their_state),
color=_STATE_COLOR)
if role is CLIENT:
edges.e(DONE, MIGHT_SWITCH_PROTOCOL,
"Potential Upgrade:<BR/>or CONNECT pending",
_STATE_COLOR,
italicize=True)
edges.e(MIGHT_SWITCH_PROTOCOL, DONE,
"No potential Upgrade:<BR/>or CONNECT pending",
_STATE_COLOR,
italicize=True)
edges.e(DONE, MUST_CLOSE, "keep-alive<BR/>is disabled", _STATE_COLOR,
italicize=True)
edges.e(DONE, IDLE, "start_next_cycle()", _SPECIAL_COLOR)
edges.write(f)
# For some reason labelfontsize doesn't seem to do anything, but this
# works
f.write(finish(role))
my_dir = os.path.dirname(__file__)
out_dir = os.path.join(my_dir, "_static")
if not os.path.exists(out_dir):
os.path.mkdir(out_dir)
for role in (CLIENT, SERVER):
dot_path = os.path.join(out_dir, str(role) + ".dot")
svg_path = dot_path[:-3] + "svg"
make_dot(role, dot_path)
subprocess.check_call(["dot", "-Tsvg", dot_path, "-o", svg_path])
dot_path = os.path.join(out_dir, "special-states.dot")
svg_path = dot_path[:-3] + "svg"
make_dot_special_state(dot_path)
subprocess.check_call(["dot", "-Tsvg", dot_path, "-o", svg_path])
|