summaryrefslogtreecommitdiff
path: root/cherrypy/_cpnative_server.py
blob: e9671d2892094f5a6ef79abfd338f3a7ff8bc39f (plain)
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
"""Native adapter for serving CherryPy via its builtin server."""

import logging
import sys
import io

import cheroot.server

import cherrypy
from cherrypy._cperror import format_exc, bare_error
from cherrypy.lib import httputil
from ._cpcompat import tonative


class NativeGateway(cheroot.server.Gateway):
    """Native gateway implementation allowing to bypass WSGI."""

    recursive = False

    def respond(self):
        """Obtain response from CherryPy machinery and then send it."""
        req = self.req
        try:
            # Obtain a Request object from CherryPy
            local = req.server.bind_addr  # FIXME: handle UNIX sockets
            local = tonative(local[0]), local[1]
            local = httputil.Host(local[0], local[1], '')
            remote = tonative(req.conn.remote_addr), req.conn.remote_port
            remote = httputil.Host(remote[0], remote[1], '')

            scheme = tonative(req.scheme)
            sn = cherrypy.tree.script_name(tonative(req.uri or '/'))
            if sn is None:
                self.send_response('404 Not Found', [], [''])
            else:
                app = cherrypy.tree.apps[sn]
                method = tonative(req.method)
                path = tonative(req.path)
                qs = tonative(req.qs or '')
                headers = (
                    (tonative(h), tonative(v))
                    for h, v in req.inheaders.items()
                )
                rfile = req.rfile
                prev = None

                try:
                    redirections = []
                    while True:
                        request, response = app.get_serving(
                            local, remote, scheme, 'HTTP/1.1')
                        request.multithread = True
                        request.multiprocess = False
                        request.app = app
                        request.prev = prev

                        # Run the CherryPy Request object and obtain the
                        # response
                        try:
                            request.run(
                                method, path, qs,
                                tonative(req.request_protocol),
                                headers, rfile,
                            )
                            break
                        except cherrypy.InternalRedirect:
                            ir = sys.exc_info()[1]
                            app.release_serving()
                            prev = request

                            if not self.recursive:
                                if ir.path in redirections:
                                    raise RuntimeError(
                                        'InternalRedirector visited the same '
                                        'URL twice: %r' % ir.path)
                                else:
                                    # Add the *previous* path_info + qs to
                                    # redirections.
                                    if qs:
                                        qs = '?' + qs
                                    redirections.append(sn + path + qs)

                            # Munge environment and try again.
                            method = 'GET'
                            path = ir.path
                            qs = ir.query_string
                            rfile = io.BytesIO()

                    self.send_response(
                        response.output_status, response.header_list,
                        response.body)
                finally:
                    app.release_serving()
        except Exception:
            tb = format_exc()
            # print tb
            cherrypy.log(tb, 'NATIVE_ADAPTER', severity=logging.ERROR)
            s, h, b = bare_error()
            self.send_response(s, h, b)

    def send_response(self, status, headers, body):
        """Send response to HTTP request."""
        req = self.req

        # Set response status
        req.status = status or b'500 Server Error'

        # Set response headers
        for header, value in headers:
            req.outheaders.append((header, value))
        if (req.ready and not req.sent_headers):
            req.sent_headers = True
            req.send_headers()

        # Set response body
        for seg in body:
            req.write(seg)


class CPHTTPServer(cheroot.server.HTTPServer):
    """Wrapper for cheroot.server.HTTPServer.

    cheroot has been designed to not reference CherryPy in any way,
    so that it can be used in other frameworks and applications.
    Therefore, we wrap it here, so we can apply some attributes
    from config -> cherrypy.server -> HTTPServer.
    """

    def __init__(self, server_adapter=cherrypy.server):
        """Initialize CPHTTPServer."""
        self.server_adapter = server_adapter

        server_name = (self.server_adapter.socket_host or
                       self.server_adapter.socket_file or
                       None)

        cheroot.server.HTTPServer.__init__(
            self, server_adapter.bind_addr, NativeGateway,
            minthreads=server_adapter.thread_pool,
            maxthreads=server_adapter.thread_pool_max,
            server_name=server_name)

        self.max_request_header_size = (
            self.server_adapter.max_request_header_size or 0)
        self.max_request_body_size = (
            self.server_adapter.max_request_body_size or 0)
        self.request_queue_size = self.server_adapter.socket_queue_size
        self.timeout = self.server_adapter.socket_timeout
        self.shutdown_timeout = self.server_adapter.shutdown_timeout
        self.protocol = self.server_adapter.protocol_version
        self.nodelay = self.server_adapter.nodelay

        ssl_module = self.server_adapter.ssl_module or 'pyopenssl'
        if self.server_adapter.ssl_context:
            adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module)
            self.ssl_adapter = adapter_class(
                self.server_adapter.ssl_certificate,
                self.server_adapter.ssl_private_key,
                self.server_adapter.ssl_certificate_chain,
                self.server_adapter.ssl_ciphers)
            self.ssl_adapter.context = self.server_adapter.ssl_context
        elif self.server_adapter.ssl_certificate:
            adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module)
            self.ssl_adapter = adapter_class(
                self.server_adapter.ssl_certificate,
                self.server_adapter.ssl_private_key,
                self.server_adapter.ssl_certificate_chain,
                self.server_adapter.ssl_ciphers)