예제 #1
0
    def __init__(self, response=None, status=None, headers=None,
                 mimetype=None, content_type=None):
        """Response can be any kind of iterable or string.  If it's a string
        it's considered being an iterable with one item which is the string
        passed.  Headers can be a list of tuples or a `Headers` object.

        Special note for `mimetype` and `content_type`.  For most mime types
        `mimetype` and `content_type` work the same, the difference affects
        only 'text' mimetypes.  If the mimetype passed with `mimetype` is a
        mimetype starting with `text/` it becomes a charset parameter defined
        with the charset of the response object.  In constrast the
        `content_type` parameter is always added as header unmodified.
        """
        if response is None:
            self.response = []
        elif isinstance(response, basestring):
            self.response = [response]
        else:
            self.response = iter(response)
        if not headers:
            self.headers = Headers()
        elif isinstance(headers, Headers):
            self.headers = headers
        else:
            self.headers = Headers(headers)
        if content_type is None:
            if mimetype is None and 'Content-Type' not in self.headers:
                mimetype = self.default_mimetype
            if mimetype is not None:
                mimetype = get_content_type(mimetype, self.charset)
            content_type = mimetype
        if content_type is not None:
            self.headers['Content-Type'] = content_type
        if status is None:
            status = self.default_status
        if isinstance(status, (int, long)):
            self.status_code = status
        else:
            self.status = status
예제 #2
0
    def __init__(self, response=None, status=200, headers=None, mimetype=None,
                 content_type=None):
        """
        Response can be any kind of iterable or string.  If it's a string it's
        considered being an iterable with one item which is the string passed.
        headers can be a list of tuples or a `Headers` object.

        Special note for `mimetype` and `content_type`.  For most mime types
        `mimetype` and `content_type` work the same, the difference affects
        only 'text' mimetypes.  If the mimetype passed with `mimetype` is a
        mimetype starting with `text/` it becomes a charset parameter defined
        with the charset of the response object.  In constrast the
        `content_type` parameter is always added as header unmodified.
        """
        if response is None:
            self.response = []
        elif isinstance(response, basestring):
            self.response = [response]
        else:
            self.response = iter(response)
        if not headers:
            self.headers = Headers()
        elif isinstance(headers, Headers):
            self.headers = headers
        else:
            self.headers = Headers(headers)
        if content_type is None:
            if mimetype is None and 'Content-Type' not in self.headers:
                mimetype = self.default_mimetype
            if mimetype is not None and mimetype.startswith('text/'):
                mimetype += '; charset=' + self.charset
            content_type = mimetype
        if content_type is not None:
            self.headers['Content-Type'] = content_type
        if isinstance(status, (int, long)):
            self.status_code = status
        else:
            self.status = status
예제 #3
0
class BaseResponse(object):
    """Base response class.  The most important fact about a response object
    is that it's a regular WSGI application.  It's initialized with a couple
    of response parameters (headers, body, status code etc.) and will start a
    valid WSGI response when called with the environ and start response
    callable.

    Because it's a WSGI application itself processing usually ends before the
    actual response is sent to the server.  This helps debugging systems
    because they can catch all the exceptions before responses are started.

    Here a small example WSGI application that takes advantage of the
    response objects::

        from werkzeug import BaseResponse as Response

        def index():
            return Response('Index page')

        def application(environ, start_response):
            path = environ.get('PATH_INFO') or '/'
            if path == '/':
                response = index()
            else:
                response = Response('Not Found', status=404)
            return response(environ, start_response)

    Like `BaseRequest` which object is lacking a lot of functionality
    implemented in mixins.  This gives you a better control about the actual
    API of your response objects, so you can create subclasses and add custom
    functionality.  A full featured response object is available as `Response`
    which implements a couple of useful mixins.

    To enforce a new type of already existing responses you can use the
    `force_type` method.  This is useful if you're working with different
    subclasses of response objects and you want to post process them with a
    know interface.

    Per default the request object will assume all the text data is `utf-8`
    encoded.  Please refer to `the unicode chapter <unicode.txt>`_ for more
    details about customizing the behavior.
    """
    charset = 'utf-8'
    default_status = 200
    default_mimetype = 'text/plain'

    def __init__(self, response=None, status=None, headers=None,
                 mimetype=None, content_type=None):
        """Response can be any kind of iterable or string.  If it's a string
        it's considered being an iterable with one item which is the string
        passed.  Headers can be a list of tuples or a `Headers` object.

        Special note for `mimetype` and `content_type`.  For most mime types
        `mimetype` and `content_type` work the same, the difference affects
        only 'text' mimetypes.  If the mimetype passed with `mimetype` is a
        mimetype starting with `text/` it becomes a charset parameter defined
        with the charset of the response object.  In constrast the
        `content_type` parameter is always added as header unmodified.
        """
        if response is None:
            self.response = []
        elif isinstance(response, basestring):
            self.response = [response]
        else:
            self.response = iter(response)
        if not headers:
            self.headers = Headers()
        elif isinstance(headers, Headers):
            self.headers = headers
        else:
            self.headers = Headers(headers)
        if content_type is None:
            if mimetype is None and 'Content-Type' not in self.headers:
                mimetype = self.default_mimetype
            if mimetype is not None:
                mimetype = get_content_type(mimetype, self.charset)
            content_type = mimetype
        if content_type is not None:
            self.headers['Content-Type'] = content_type
        if status is None:
            status = self.default_status
        if isinstance(status, (int, long)):
            self.status_code = status
        else:
            self.status = status

    def force_type(cls, response, environ=None):
        """Enforce that the WSGI response is a response object of the current
        type.  Werkzeug will use the `BaseResponse` internally in many
        situations like the exceptions.  If you call `get_response` on an
        exception you will get back a regular `BaseResponse` object, even if
        you are using a custom subclass.

        This method can enforce a given response type, and it will also
        convert arbitrary WSGI callables into response objects if an environ
        is provided::

            # convert a Werkzeug response object into an instance of the
            # MyResponseClass subclass.
            response = MyResponseClass.force_type(response)

            # convert any WSGI application into a response object
            response = MyResponseClass.force_type(response, environ)

        This is especially useful if you want to post-process responses in
        the main dispatcher and use functionality provided by your subclass.

        Keep in mind that this will modify response objects in place if
        possible!
        """
        if not isinstance(response, BaseResponse):
            if environ is None:
                raise TypeError('cannot convert WSGI application into '
                                'response objects without an environ')
            response = BaseResponse(*run_wsgi_app(response, environ))
        response.__class__ = cls
        return response
    force_type = classmethod(force_type)

    def from_app(cls, app, environ, buffered=False):
        """Create a new response object from an application output.  This
        works best if you pass it an application that returns a generator all
        the time.  Sometimes applications may use the `write()` callable
        returned by the `start_response` function.  This tries to resolve such
        edge cases automatically.  But if you don't get the expected output
        you should set `buffered` to `True` which enforces buffering.
        """
        return cls(*run_wsgi_app(app, environ, buffered))
    from_app = classmethod(from_app)

    def _get_status_code(self):
        try:
            return int(self.status.split(None, 1)[0])
        except ValueError:
            return 0
    def _set_status_code(self, code):
        try:
            self.status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper())
        except KeyError:
            self.status = '%d UNKNOWN' % code
    status_code = property(_get_status_code, _set_status_code,
                           'The HTTP Status code as number')
    del _get_status_code, _set_status_code

    def _get_data(self):
        """The string representation of the request body.  Whenever you access
        this property the request iterable is encoded and flattened.  This
        can lead to unwanted behavior if you stream big data.
        """
        if not isinstance(self.response, list):
            self.response = list(self.response)
        return ''.join(self.iter_encoded())
    def _set_data(self, value):
        self.response = [value]
    data = property(_get_data, _set_data, doc=_get_data.__doc__)
    del _get_data, _set_data

    def iter_encoded(self, charset=None):
        """Iter the response encoded with the encoding specified.  If no
        encoding is given the encoding from the class is used.  Note that
        this does not encode data that is already a bytestring.
        """
        charset = charset or self.charset or 'ascii'
        for item in self.response:
            if isinstance(item, unicode):
                yield item.encode(charset)
            else:
                yield str(item)

    def set_cookie(self, key, value='', max_age=None, expires=None,
                   path='/', domain=None, secure=None, httponly=False):
        """Sets a cookie. The parameters are the same as in the cookie `Morsel`
        object in the Python standard library but it accepts unicode data too:

        - `max_age` should be a number of seconds, or `None` (default) if the
           cookie should last only as long as the client’s browser session.
        - `expires` should be a `datetime` object or UNIX timestamp.
        - Use `domain` if you want to set a cross-domain cookie.  For example,
          ``domain=".example.com"`` will set a cookie that is readable by the
          domain ``www.example.com``, ``foo.example.com`` etc.  Otherwise, a
          cookie will only be readable by the domain that set it.
        - `path` limits the cookie to a given path, per default it will span
          the whole domain.
        """
        self.headers.add('Set-Cookie', dump_cookie(key, value, max_age,
                         expires, path, domain, secure, httponly,
                         self.charset))

    def delete_cookie(self, key, path='/', domain=None):
        """Delete a cookie.  Fails silently if key doesn't exist."""
        self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)

    def header_list(self):
        """This returns the headers in the target charset as list.  It's used
        in __call__ to get the headers for the response.
        """
        return self.headers.to_list(self.charset)
    header_list = property(header_list, doc=header_list.__doc__)

    def is_streamed(self):
        """If the response is streamed (the response is not a sequence) this
        property is `True`.  In this case streamed means that there is no
        information about the number of iterations.  This is usully `True`
        if a generator is passed to the response object.

        This is useful for checking before applying some sort of post
        filtering that should not take place for streamed responses.
        """
        try:
            len(self.response)
        except TypeError:
            return False
        return True
    is_streamed = property(is_streamed, doc=is_streamed.__doc__)

    def fix_headers(self, environ):
        """This is automatically called right before the response is started
        and should fix common mistakes in headers.  For example location
        headers are joined with the root URL here.
        """
        if 'Location' in self.headers:
            self.headers['Location'] = urlparse.urljoin(
                get_current_url(environ, root_only=True),
                self.headers['Location']
            )

    def close(self):
        """Close the wrapped response if possible."""
        if hasattr(self.response, 'close'):
            self.response.close()

    def freeze(self):
        """Call this method if you want to make your response object ready for
        pickeling.  This buffers the generator if there is one."""
        BaseResponse.data.__get__(self)

    def __call__(self, environ, start_response):
        """Process this response as WSGI application."""
        self.fix_headers(environ)
        if environ['REQUEST_METHOD'] == 'HEAD':
            resp = ()
        elif 100 <= self.status_code < 200 or self.status_code in (204, 304):
            self.headers['Content-Length'] = 0
            resp = ()
        else:
            resp = self.iter_encoded()
        start_response(self.status, self.header_list)
        return resp
예제 #4
0
class BaseResponse(object):
    """
    Base response class.
    """
    charset = 'utf-8'
    default_mimetype = 'text/plain'

    def __init__(self, response=None, status=200, headers=None, mimetype=None,
                 content_type=None):
        """
        Response can be any kind of iterable or string.  If it's a string it's
        considered being an iterable with one item which is the string passed.
        headers can be a list of tuples or a `Headers` object.

        Special note for `mimetype` and `content_type`.  For most mime types
        `mimetype` and `content_type` work the same, the difference affects
        only 'text' mimetypes.  If the mimetype passed with `mimetype` is a
        mimetype starting with `text/` it becomes a charset parameter defined
        with the charset of the response object.  In constrast the
        `content_type` parameter is always added as header unmodified.
        """
        if response is None:
            self.response = []
        elif isinstance(response, basestring):
            self.response = [response]
        else:
            self.response = iter(response)
        if not headers:
            self.headers = Headers()
        elif isinstance(headers, Headers):
            self.headers = headers
        else:
            self.headers = Headers(headers)
        if content_type is None:
            if mimetype is None and 'Content-Type' not in self.headers:
                mimetype = self.default_mimetype
            if mimetype is not None and mimetype.startswith('text/'):
                mimetype += '; charset=' + self.charset
            content_type = mimetype
        if content_type is not None:
            self.headers['Content-Type'] = content_type
        if isinstance(status, (int, long)):
            self.status_code = status
        else:
            self.status = status

    def from_app(cls, app, environ, buffered=False):
        """
        Create a new response object from an application output.  This works
        best if you pass it an application that returns a generator all the
        time.  Sometimes applications may use the `write()` callable returned
        by the `start_response` function.  This tries to resolve such edge
        cases automatically.  But if you don't get the expected output you
        should set `buffered` to `True` which enforces buffering.
        """
        return cls(*run_wsgi_app(app, environ, buffered))
    from_app = classmethod(from_app)

    def _get_status_code(self):
        return int(self.status.split(None, 1)[0])
    def _set_status_code(self, code):
        self.status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper())
    status_code = property(_get_status_code, _set_status_code,
                           'Get the HTTP Status code as number')
    del _get_status_code, _set_status_code

    def cache_control(self):
        def on_update(cache_control):
            if not cache_control and 'cache-control' in self.headers:
                del self.headers['cache-control']
            elif cache_control:
                self.headers['Cache-Control'] = cache_control.to_header()
        value = self.headers.get('Cache-Control')
        if value is not None:
            value = parse_cache_control_header(value)
        return CacheControl(value, on_update)
    cache_control = lazy_property(cache_control)

    def write(self, data):
        """If we have a buffered response this writes to the buffer."""
        if not isinstance(self.response, list):
            raise RuntimeError('cannot write to a streamed response.')
        self.response.append(data)

    def writelines(self, lines):
        """Write lines."""
        self.write(''.join(lines))

    def _get_response_body(self):
        """
        The string representation of the request body.  Whenever you access
        this property the request iterable is encoded and flattened.  This
        can lead to unwanted behavior if you stream big data.
        """
        if not isinstance(self.response, list):
            self.response = list(self.response)
        return ''.join(self.iter_encoded())
    def _set_response_body(self, value):
        """Set a new string as response body."""
        self.response = [value]
    response_body = property(_get_response_body, _set_response_body,
                             doc=_get_response_body.__doc__)
    del _get_response_body, _set_response_body

    def iter_encoded(self, charset=None):
        """
        Iter the response encoded with the encoding specified.  If no
        encoding is given the encoding from the class is used.  Note that
        this does not encode data that is already a bytestring.
        """
        charset = charset or self.charset or 'ascii'
        for item in self.response:
            if isinstance(item, unicode):
                yield item.encode(charset)
            else:
                yield str(item)

    def set_cookie(self, key, value='', max_age=None, expires=None,
                   path='/', domain=None, secure=None, httponly=False):
        """Set a new cookie."""
        try:
            key = str(key)
        except UnicodeError:
            raise TypeError('invalid key %r' % key)
        c = SimpleCookie()
        if isinstance(value, unicode):
            value = value.encode(self.charset)
        c[key] = value
        if expires is not None:
            if not isinstance(expires, basestring):
                expires = cookie_date(expires)
            c[key]['expires'] = expires
        for k, v in (('path', path), ('domain', domain), ('secure', secure),
                     ('max-age', max_age), ('HttpOnly', httponly)):
            if v is not None and v is not False:
                c[key][k] = str(v)
        self.headers.add('Set-Cookie', c[key].output(header='').lstrip())

    def delete_cookie(self, key, path='/', domain=None):
        """Delete a cookie."""
        self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)

    def header_list(self):
        """
        This returns the headers in the target charset as list.  It's used in
        __call__ to get the headers for the response.
        """
        return self.headers.to_list(self.charset)
    header_list = property(header_list, doc=header_list.__doc__)

    def is_streamed(self):
        """
        If the request is streamed (the response is not a sequence) this
        property is `True`.
        """
        try:
            len(self.response)
        except TypeError:
            return False
        return True
    is_streamed = property(is_streamed, doc=is_streamed.__doc__)

    def fix_headers(self, environ):
        """
        This is automatically called right before the response is started
        and should fix common mistakes in headers.  For example location
        headers are joined with the root URL here.
        """
        if 'Location' in self.headers:
            self.headers['Location'] = urlparse.urljoin(
                get_current_url(environ, host_only=True),
                self.headers['Location']
            )

    def make_conditional(self, request_or_environ):
        """
        Make the response conditional to the request.  This method works best
        if an etag was defined for the response already.  The `add_etag`
        method can be used to do that.  If called without etag just the date
        header is set.

        This does nothing if the request method in the request or enviorn is
        anything but GET.
        """
        if environ['REQUEST_METHOD'] not in ('GET', 'HEAD'):
            return
        environ = getattr(request_or_environ, 'environ', request_or_environ)
        self.headers['Date'] = http_date()
        if 'etag' in self.headers:
            if_none_match = environ.get('HTTP_IF_NONE_MATCH')
            last_modified = self.headers.get('last-modified')
            if_modified_since = environ.get('HTTP_IF_MODIFIED_SINCE')
            # we only set the status code because the request object removes
            # contents for 304 responses automatically on `__call__`
            if if_none_match and if_none_match == self.headers['etag'] or \
               if_modified_since == last_modified:
                self.status_code = 304

    def add_etag(self, overwrite=False):
        """Add an etag for the current response if there is none yet."""
        if not overwrite and 'etag' in self.headers:
            return
        etag = md5(self.response_body).hexdigest()
        self.headers['ETag'] = etag

    def close(self):
        """Close the wrapped response if possible."""
        if hasattr(self.response, 'close'):
            self.response.close()

    def __call__(self, environ, start_response):
        """Process this response as WSGI application."""
        self.fix_headers(environ)
        if environ['REQUEST_METHOD'] == 'HEAD':
            resp = ()
        elif 100 <= self.status_code < 200 or self.status_code in (204, 304):
            self.headers['Content-Length'] = 0
            resp = ()
        else:
            resp = self.iter_encoded()
        start_response(self.status, self.header_list)
        return resp