1"""Manage HTTP servers with CherryPy."""
2
3import six
4
5import cherrypy
6from cherrypy.lib.reprconf import attributes
7from cherrypy._cpcompat import text_or_bytes
8from cherrypy.process.servers import ServerAdapter
9
10
11__all__ = ('Server', )
12
13
14class Server(ServerAdapter):
15    """An adapter for an HTTP server.
16
17    You can set attributes (like socket_host and socket_port)
18    on *this* object (which is probably cherrypy.server), and call
19    quickstart. For example::
20
21        cherrypy.server.socket_port = 80
22        cherrypy.quickstart()
23    """
24
25    socket_port = 8080
26    """The TCP port on which to listen for connections."""
27
28    _socket_host = '127.0.0.1'
29
30    @property
31    def socket_host(self):  # noqa: D401; irrelevant for properties
32        """The hostname or IP address on which to listen for connections.
33
34        Host values may be any IPv4 or IPv6 address, or any valid hostname.
35        The string 'localhost' is a synonym for '127.0.0.1' (or '::1', if
36        your hosts file prefers IPv6). The string '0.0.0.0' is a special
37        IPv4 entry meaning "any active interface" (INADDR_ANY), and '::'
38        is the similar IN6ADDR_ANY for IPv6. The empty string or None are
39        not allowed.
40        """
41        return self._socket_host
42
43    @socket_host.setter
44    def socket_host(self, value):
45        if value == '':
46            raise ValueError("The empty string ('') is not an allowed value. "
47                             "Use '0.0.0.0' instead to listen on all active "
48                             'interfaces (INADDR_ANY).')
49        self._socket_host = value
50
51    socket_file = None
52    """If given, the name of the UNIX socket to use instead of TCP/IP.
53
54    When this option is not None, the `socket_host` and `socket_port` options
55    are ignored."""
56
57    socket_queue_size = 5
58    """The 'backlog' argument to socket.listen(); specifies the maximum number
59    of queued connections (default 5)."""
60
61    socket_timeout = 10
62    """The timeout in seconds for accepted connections (default 10)."""
63
64    accepted_queue_size = -1
65    """The maximum number of requests which will be queued up before
66    the server refuses to accept it (default -1, meaning no limit)."""
67
68    accepted_queue_timeout = 10
69    """The timeout in seconds for attempting to add a request to the
70    queue when the queue is full (default 10)."""
71
72    shutdown_timeout = 5
73    """The time to wait for HTTP worker threads to clean up."""
74
75    protocol_version = 'HTTP/1.1'
76    """The version string to write in the Status-Line of all HTTP responses,
77    for example, "HTTP/1.1" (the default). Depending on the HTTP server used,
78    this should also limit the supported features used in the response."""
79
80    thread_pool = 10
81    """The number of worker threads to start up in the pool."""
82
83    thread_pool_max = -1
84    """The maximum size of the worker-thread pool. Use -1 to indicate no limit.
85    """
86
87    max_request_header_size = 500 * 1024
88    """The maximum number of bytes allowable in the request headers.
89    If exceeded, the HTTP server should return "413 Request Entity Too Large".
90    """
91
92    max_request_body_size = 100 * 1024 * 1024
93    """The maximum number of bytes allowable in the request body. If exceeded,
94    the HTTP server should return "413 Request Entity Too Large"."""
95
96    instance = None
97    """If not None, this should be an HTTP server instance (such as
98    cheroot.wsgi.Server) which cherrypy.server will control.
99    Use this when you need
100    more control over object instantiation than is available in the various
101    configuration options."""
102
103    ssl_context = None
104    """When using PyOpenSSL, an instance of SSL.Context."""
105
106    ssl_certificate = None
107    """The filename of the SSL certificate to use."""
108
109    ssl_certificate_chain = None
110    """When using PyOpenSSL, the certificate chain to pass to
111    Context.load_verify_locations."""
112
113    ssl_private_key = None
114    """The filename of the private key to use with SSL."""
115
116    ssl_ciphers = None
117    """The ciphers list of SSL."""
118
119    if six.PY3:
120        ssl_module = 'builtin'
121        """The name of a registered SSL adaptation module to use with
122        the builtin WSGI server. Builtin options are: 'builtin' (to
123        use the SSL library built into recent versions of Python).
124        You may also register your own classes in the
125        cheroot.server.ssl_adapters dict."""
126    else:
127        ssl_module = 'pyopenssl'
128        """The name of a registered SSL adaptation module to use with the
129        builtin WSGI server. Builtin options are 'builtin' (to use the SSL
130        library built into recent versions of Python) and 'pyopenssl' (to
131        use the PyOpenSSL project, which you must install separately). You
132        may also register your own classes in the cheroot.server.ssl_adapters
133        dict."""
134
135    statistics = False
136    """Turns statistics-gathering on or off for aware HTTP servers."""
137
138    nodelay = True
139    """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
140
141    wsgi_version = (1, 0)
142    """The WSGI version tuple to use with the builtin WSGI server.
143    The provided options are (1, 0) [which includes support for PEP 3333,
144    which declares it covers WSGI version 1.0.1 but still mandates the
145    wsgi.version (1, 0)] and ('u', 0), an experimental unicode version.
146    You may create and register your own experimental versions of the WSGI
147    protocol by adding custom classes to the cheroot.server.wsgi_gateways dict.
148    """
149
150    peercreds = False
151    """If True, peer cred lookup for UNIX domain socket will put to WSGI env.
152
153    This information will then be available through WSGI env vars:
154    * X_REMOTE_PID
155    * X_REMOTE_UID
156    * X_REMOTE_GID
157    """
158
159    peercreds_resolve = False
160    """If True, username/group will be looked up in the OS from peercreds.
161
162    This information will then be available through WSGI env vars:
163    * REMOTE_USER
164    * X_REMOTE_USER
165    * X_REMOTE_GROUP
166    """
167
168    def __init__(self):
169        """Initialize Server instance."""
170        self.bus = cherrypy.engine
171        self.httpserver = None
172        self.interrupt = None
173        self.running = False
174
175    def httpserver_from_self(self, httpserver=None):
176        """Return a (httpserver, bind_addr) pair based on self attributes."""
177        if httpserver is None:
178            httpserver = self.instance
179        if httpserver is None:
180            from cherrypy import _cpwsgi_server
181            httpserver = _cpwsgi_server.CPWSGIServer(self)
182        if isinstance(httpserver, text_or_bytes):
183            # Is anyone using this? Can I add an arg?
184            httpserver = attributes(httpserver)(self)
185        return httpserver, self.bind_addr
186
187    def start(self):
188        """Start the HTTP server."""
189        if not self.httpserver:
190            self.httpserver, self.bind_addr = self.httpserver_from_self()
191        super(Server, self).start()
192    start.priority = 75
193
194    @property
195    def bind_addr(self):
196        """Return bind address.
197
198        A (host, port) tuple for TCP sockets or a str for Unix domain sockts.
199        """
200        if self.socket_file:
201            return self.socket_file
202        if self.socket_host is None and self.socket_port is None:
203            return None
204        return (self.socket_host, self.socket_port)
205
206    @bind_addr.setter
207    def bind_addr(self, value):
208        if value is None:
209            self.socket_file = None
210            self.socket_host = None
211            self.socket_port = None
212        elif isinstance(value, text_or_bytes):
213            self.socket_file = value
214            self.socket_host = None
215            self.socket_port = None
216        else:
217            try:
218                self.socket_host, self.socket_port = value
219                self.socket_file = None
220            except ValueError:
221                raise ValueError('bind_addr must be a (host, port) tuple '
222                                 '(for TCP sockets) or a string (for Unix '
223                                 'domain sockets), not %r' % value)
224
225    def base(self):
226        """Return the base for this server.
227
228        e.i. scheme://host[:port] or sock file
229        """
230        if self.socket_file:
231            return self.socket_file
232
233        host = self.socket_host
234        if host in ('0.0.0.0', '::'):
235            # 0.0.0.0 is INADDR_ANY and :: is IN6ADDR_ANY.
236            # Look up the host name, which should be the
237            # safest thing to spit out in a URL.
238            import socket
239            host = socket.gethostname()
240
241        port = self.socket_port
242
243        if self.ssl_certificate:
244            scheme = 'https'
245            if port != 443:
246                host += ':%s' % port
247        else:
248            scheme = 'http'
249            if port != 80:
250                host += ':%s' % port
251
252        return '%s://%s' % (scheme, host)
253