Example #1
0
File: render.py Project: iotrl/eve
def _prepare_response(resource, dct, last_modified=None, etag=None,
                      status=200, headers=None):
    """ Prepares the response object according to the client request and
    available renderers, making sure that all accessory directives (caching,
    etag, last-modified) are present.

    :param resource: the resource involved.
    :param dct: the dict that should be sent back as a response.
    :param last_modified: Last-Modified header value.
    :param etag: ETag header value.
    :param status: response status.

    .. versionchanged:: 0.7
       Add support for regexes in X_DOMAINS_RE. Closes #660, #974.
       ETag value now surrounded by double quotes. Closes #794.

    .. versionchanged:: 0.6
       JSONP Support.

    .. versionchanged:: 0.4
       Support for optional extra headers.
       Fix #381. 500 instead of 404 if CORS is enabled.

    .. versionchanged:: 0.3
       Support for X_MAX_AGE.

    .. versionchanged:: 0.1.0
       Support for optional HATEOAS.

    .. versionchanged:: 0.0.9
       Support for Python 3.3.

    .. versionchanged:: 0.0.7
       Support for Rate-Limiting.

    .. versionchanged:: 0.0.6
       Support for HEAD requests.

    .. versionchanged:: 0.0.5
       Support for Cross-Origin Resource Sharing (CORS).

    .. versionadded:: 0.0.4
    """
    if request.method == 'OPTIONS':
        resp = app.make_default_options_response()
    else:
        # obtain the best match between client's request and available mime
        # types, along with the corresponding render function.
        mime, renderer_cls = _best_mime()

        # invoke the render function and obtain the corresponding rendered item
        rendered = renderer_cls().render(dct)

        # JSONP
        if config.JSONP_ARGUMENT:
            jsonp_arg = config.JSONP_ARGUMENT
            if jsonp_arg in request.args and 'json' in mime:
                callback = request.args.get(jsonp_arg)
                rendered = "%s(%s)" % (callback, rendered)

        # build the main wsgi response object
        resp = make_response(rendered, status)
        resp.mimetype = mime

    # extra headers
    if headers:
        for header, value in headers:
            if header != 'Content-Type':
                resp.headers.add(header, value)

    # cache directives
    if request.method in ('GET', 'HEAD'):
        if resource:
            cache_control = config.DOMAIN[resource]['cache_control']
            expires = config.DOMAIN[resource]['cache_expires']
        else:
            cache_control = config.CACHE_CONTROL
            expires = config.CACHE_EXPIRES
        if cache_control:
            resp.headers.add('Cache-Control', cache_control)
        if expires:
            resp.expires = time.time() + expires

    # etag and last-modified
    if etag:
        resp.headers.add('ETag', '"' + etag + '"')
    if last_modified:
        resp.headers.add('Last-Modified', date_to_rfc1123(last_modified))

    # CORS
    origin = request.headers.get('Origin')
    if origin and (config.X_DOMAINS or config.X_DOMAINS_RE):
        if config.X_DOMAINS is None:
            domains = []
        elif isinstance(config.X_DOMAINS, str):
            domains = [config.X_DOMAINS]
        else:
            domains = config.X_DOMAINS

        if config.X_DOMAINS_RE is None:
            domains_re = []
        elif isinstance(config.X_DOMAINS_RE, str):
            domains_re = [config.X_DOMAINS_RE]
        else:
            domains_re = config.X_DOMAINS_RE

        # precompile regexes and ignore invalids
        domains_re_compiled = []
        for domain_re in domains_re:
            try:
                domains_re_compiled.append(re.compile(domain_re))
            except re.error:
                continue

        if config.X_HEADERS is None:
            headers = []
        elif isinstance(config.X_HEADERS, str):
            headers = [config.X_HEADERS]
        else:
            headers = config.X_HEADERS

        if config.X_EXPOSE_HEADERS is None:
            expose_headers = []
        elif isinstance(config.X_EXPOSE_HEADERS, str):
            expose_headers = [config.X_EXPOSE_HEADERS]
        else:
            expose_headers = config.X_EXPOSE_HEADERS

        # The only accepted value for Access-Control-Allow-Credentials header
        # is "true"
        allow_credentials = config.X_ALLOW_CREDENTIALS is True

        methods = app.make_default_options_response().headers.get('allow', '')

        if '*' in domains:
            resp.headers.add('Access-Control-Allow-Origin', origin)
            resp.headers.add('Vary', 'Origin')
        elif any(origin == domain for domain in domains):
            resp.headers.add('Access-Control-Allow-Origin', origin)
        elif any(domain.match(origin) for domain in domains_re_compiled):
            resp.headers.add('Access-Control-Allow-Origin', origin)
        else:
            resp.headers.add('Access-Control-Allow-Origin', '')
        resp.headers.add('Access-Control-Allow-Headers', ', '.join(headers))
        resp.headers.add('Access-Control-Expose-Headers',
                         ', '.join(expose_headers))
        resp.headers.add('Access-Control-Allow-Methods', methods)
        resp.headers.add('Access-Control-Max-Age', config.X_MAX_AGE)
        if allow_credentials:
            resp.headers.add('Access-Control-Allow-Credentials', "true")

    # Rate-Limiting
    limit = get_rate_limit()
    if limit and limit.send_x_headers:
        resp.headers.add('X-RateLimit-Remaining', str(limit.remaining))
        resp.headers.add('X-RateLimit-Limit', str(limit.limit))
        resp.headers.add('X-RateLimit-Reset', str(limit.reset))

    return resp
Example #2
0
def _prepare_response(resource, dct, last_modified=None, etag=None,
                      status=200):
    """ Prepares the response object according to the client request and
    available renderers, making sure that all accessory directives (caching,
    etag, last-modified) are present.

    :param resource: the resource involved.
    :param dct: the dict that should be sent back as a response.
    :param last_modified: Last-Modified header value.
    :param etag: ETag header value.
    :param status: response status.

    .. versionchanged:: 0.0.7
       Support for Rate-Limiting.

    .. versionchanged:: 0.0.6
       Support for HEAD requests.

    .. versionchanged:: 0.0.5
       Support for Cross-Origin Resource Sharing (CORS).

    .. versionadded:: 0.0.4
    """
    if request.method == 'OPTIONS':
        resp = app.make_default_options_response()
    else:
        # obtain the best match between client's request and available mime
        # types, along with the corresponding render function.
        mime, renderer = _best_mime()

        # invoke the render function and obtain the corresponding rendered item
        rendered = globals()[renderer](**dct)

        # build the main wsgi rensponse object
        resp = make_response(rendered, status)
        resp.mimetype = mime

    # cache directives
    if request.method in ('GET', 'HEAD'):
        if resource:
            cache_control = config.DOMAIN[resource]['cache_control']
            expires = config.DOMAIN[resource]['cache_expires']
        else:
            cache_control = config.CACHE_CONTROL
            expires = config.CACHE_EXPIRES
        if cache_control:
            resp.headers.add('Cache-Control', cache_control)
        if expires:
            resp.expires = time.time() + expires

    # etag and last-modified
    if etag:
        resp.headers.add('ETag', etag)
    if last_modified:
        resp.headers.add('Last-Modified', date_to_str(last_modified))

    # CORS
    if 'Origin' in request.headers and config.X_DOMAINS is not None:
        if isinstance(config.X_DOMAINS, basestring):
            domains = [config.X_DOMAINS]
        else:
            domains = config.X_DOMAINS
        methods = app.make_default_options_response().headers['allow']
        resp.headers.add('Access-Control-Allow-Origin', ', '.join(domains))
        resp.headers.add('Access-Control-Allow-Methods', methods)
        resp.headers.add('Access-Control-Allow-Max-Age', 21600)

    # Rate-Limiting
    limit = get_rate_limit()
    if limit and limit.send_x_headers:
        resp.headers.add('X-RateLimit-Remaining', str(limit.remaining))
        resp.headers.add('X-RateLimit-Limit', str(limit.limit))
        resp.headers.add('X-RateLimit-Reset', str(limit.reset))

    return resp
Example #3
0
def _prepare_response(resource,
                      dct,
                      last_modified=None,
                      etag=None,
                      status=200,
                      headers=None):
    """ Prepares the response object according to the client request and
    available renderers, making sure that all accessory directives (caching,
    etag, last-modified) are present.

    :param resource: the resource involved.
    :param dct: the dict that should be sent back as a response.
    :param last_modified: Last-Modified header value.
    :param etag: ETag header value.
    :param status: response status.

    .. versionchanged:: 0.6
       JSONP Support.

    .. versionchanged:: 0.4
       Support for optional extra headers.
       Fix #381. 500 instead of 404 if CORS is enabled.

    .. versionchanged:: 0.3
       Support for X_MAX_AGE.

    .. versionchanged:: 0.1.0
       Support for optional HATEOAS.

    .. versionchanged:: 0.0.9
       Support for Python 3.3.

    .. versionchanged:: 0.0.7
       Support for Rate-Limiting.

    .. versionchanged:: 0.0.6
       Support for HEAD requests.

    .. versionchanged:: 0.0.5
       Support for Cross-Origin Resource Sharing (CORS).

    .. versionadded:: 0.0.4
    """
    if request.method == 'OPTIONS':
        resp = app.make_default_options_response()
    else:
        # obtain the best match between client's request and available mime
        # types, along with the corresponding render function.
        mime, renderer = _best_mime()

        # invoke the render function and obtain the corresponding rendered item
        rendered = globals()[renderer](dct)

        # JSONP
        if config.JSONP_ARGUMENT:
            jsonp_arg = config.JSONP_ARGUMENT
            if jsonp_arg in request.args and 'json' in mime:
                callback = request.args.get(jsonp_arg)
                rendered = "%s(%s)" % (callback, rendered)

        # build the main wsgi rensponse object
        resp = make_response(rendered, status)
        resp.mimetype = mime

    # extra headers
    if headers:
        for header, value in headers:
            if header != 'Content-Type':
                resp.headers.add(header, value)

    # cache directives
    if request.method in ('GET', 'HEAD'):
        if resource:
            cache_control = config.DOMAIN[resource]['cache_control']
            expires = config.DOMAIN[resource]['cache_expires']
        else:
            cache_control = config.CACHE_CONTROL
            expires = config.CACHE_EXPIRES
        if cache_control:
            resp.headers.add('Cache-Control', cache_control)
        if expires:
            resp.expires = time.time() + expires

    # etag and last-modified
    if etag:
        resp.headers.add('ETag', etag)
    if last_modified:
        resp.headers.add('Last-Modified', date_to_rfc1123(last_modified))

    # CORS
    origin = request.headers.get('Origin')
    if origin and config.X_DOMAINS:
        if isinstance(config.X_DOMAINS, str):
            domains = [config.X_DOMAINS]
        else:
            domains = config.X_DOMAINS

        if config.X_HEADERS is None:
            headers = []
        elif isinstance(config.X_HEADERS, str):
            headers = [config.X_HEADERS]
        else:
            headers = config.X_HEADERS

        if config.X_EXPOSE_HEADERS is None:
            expose_headers = []
        elif isinstance(config.X_EXPOSE_HEADERS, str):
            expose_headers = [config.X_EXPOSE_HEADERS]
        else:
            expose_headers = config.X_EXPOSE_HEADERS

        # The only accepted value for Access-Control-Allow-Credentials header
        # is "true"
        allow_credentials = config.X_ALLOW_CREDENTIALS is True

        methods = app.make_default_options_response().headers.get('allow', '')

        if '*' in domains:
            resp.headers.add('Access-Control-Allow-Origin', origin)
            resp.headers.add('Vary', 'Origin')
        elif origin in domains:
            resp.headers.add('Access-Control-Allow-Origin', origin)
        else:
            resp.headers.add('Access-Control-Allow-Origin', '')
        resp.headers.add('Access-Control-Allow-Headers', ', '.join(headers))
        resp.headers.add('Access-Control-Expose-Headers',
                         ', '.join(expose_headers))
        resp.headers.add('Access-Control-Allow-Methods', methods)
        resp.headers.add('Access-Control-Max-Age', config.X_MAX_AGE)
        if allow_credentials:
            resp.headers.add('Access-Control-Allow-Credentials', "true")

    # Rate-Limiting
    limit = get_rate_limit()
    if limit and limit.send_x_headers:
        resp.headers.add('X-RateLimit-Remaining', str(limit.remaining))
        resp.headers.add('X-RateLimit-Limit', str(limit.limit))
        resp.headers.add('X-RateLimit-Reset', str(limit.reset))

    return resp
Example #4
0
def _prepare_response(resource, dct, last_modified=None, etag=None,
                      status=200, headers=None):
    """ Prepares the response object according to the client request and
    available renderers, making sure that all accessory directives (caching,
    etag, last-modified) are present.

    :param resource: the resource involved.
    :param dct: the dict that should be sent back as a response.
    :param last_modified: Last-Modified header value.
    :param etag: ETag header value.
    :param status: response status.

    .. versionchanged:: 0.4
       Support for optional extra headers.
       Fix #381. 500 instead of 404 if CORS is enabled.

    .. versionchanged:: 0.3
       Support for X_MAX_AGE.

    .. versionchanged:: 0.1.0
       Support for optional HATEOAS.

    .. versionchanged:: 0.0.9
       Support for Python 3.3.

    .. versionchanged:: 0.0.7
       Support for Rate-Limiting.

    .. versionchanged:: 0.0.6
       Support for HEAD requests.

    .. versionchanged:: 0.0.5
       Support for Cross-Origin Resource Sharing (CORS).

    .. versionadded:: 0.0.4
    """
    if request.method == 'OPTIONS':
        resp = app.make_default_options_response()
    else:
        # obtain the best match between client's request and available mime
        # types, along with the corresponding render function.
        mime, renderer = _best_mime()

        # invoke the render function and obtain the corresponding rendered item
        rendered = globals()[renderer](dct)

        # build the main wsgi rensponse object
        resp = make_response(rendered, status)
        resp.mimetype = mime

    # extra headers
    if headers:
        for header, value in headers:
            if header != 'Content-Type':
                resp.headers.add(header, value)

    # cache directives
    if request.method in ('GET', 'HEAD'):
        if resource:
            cache_control = config.DOMAIN[resource]['cache_control']
            expires = config.DOMAIN[resource]['cache_expires']
        else:
            cache_control = config.CACHE_CONTROL
            expires = config.CACHE_EXPIRES
        if cache_control:
            resp.headers.add('Cache-Control', cache_control)
        if expires:
            resp.expires = time.time() + expires

    # etag and last-modified
    if etag:
        resp.headers.add('ETag', etag)
    if last_modified:
        resp.headers.add('Last-Modified', date_to_rfc1123(last_modified))

    # CORS
    origin = request.headers.get('Origin')
    if origin and config.X_DOMAINS:
        if isinstance(config.X_DOMAINS, str):
            domains = [config.X_DOMAINS]
        else:
            domains = config.X_DOMAINS

        if config.X_HEADERS is None:
            headers = []
        elif isinstance(config.X_HEADERS, str):
            headers = [config.X_HEADERS]
        else:
            headers = config.X_HEADERS

        if config.X_EXPOSE_HEADERS is None:
            expose_headers = []
        elif isinstance(config.X_EXPOSE_HEADERS, str):
            expose_headers = [config.X_EXPOSE_HEADERS]
        else:
            expose_headers = config.X_EXPOSE_HEADERS

        methods = app.make_default_options_response().headers.get('allow', '')

        if '*' in domains:
            resp.headers.add('Access-Control-Allow-Origin', origin)
            resp.headers.add('Vary', 'Origin')
        elif origin in domains:
            resp.headers.add('Access-Control-Allow-Origin', origin)
        else:
            resp.headers.add('Access-Control-Allow-Origin', '')
        resp.headers.add('Access-Control-Allow-Headers', ', '.join(headers))
        resp.headers.add('Access-Control-Expose-Headers',
                         ', '.join(expose_headers))
        resp.headers.add('Access-Control-Allow-Methods', methods)
        resp.headers.add('Access-Control-Allow-Max-Age', config.X_MAX_AGE)

    # Rate-Limiting
    limit = get_rate_limit()
    if limit and limit.send_x_headers:
        resp.headers.add('X-RateLimit-Remaining', str(limit.remaining))
        resp.headers.add('X-RateLimit-Limit', str(limit.limit))
        resp.headers.add('X-RateLimit-Reset', str(limit.reset))

    return resp
Example #5
0
def _prepare_response(resource,
                      dct,
                      last_modified=None,
                      etag=None,
                      status=200):
    """ Prepares the response object according to the client request and
    available renderers, making sure that all accessory directives (caching,
    etag, last-modified) are present.

    :param resource: the resource involved.
    :param dct: the dict that should be sent back as a response.
    :param last_modified: Last-Modified header value.
    :param etag: ETag header value.
    :param status: response status.

    .. versionchanged:: 0.0.7
       Support for Rate-Limiting.

    .. versionchanged:: 0.0.6
       Support for HEAD requests.

    .. versionchanged:: 0.0.5
       Support for Cross-Origin Resource Sharing (CORS).

    .. versionadded:: 0.0.4
    """
    if request.method == 'OPTIONS':
        resp = app.make_default_options_response()
    else:
        # obtain the best match between client's request and available mime
        # types, along with the corresponding render function.
        mime, renderer = _best_mime()

        # invoke the render function and obtain the corresponding rendered item
        rendered = globals()[renderer](**dct)

        # build the main wsgi rensponse object
        resp = make_response(rendered, status)
        resp.mimetype = mime

    # cache directives
    if request.method in ('GET', 'HEAD'):
        if resource:
            cache_control = config.DOMAIN[resource]['cache_control']
            expires = config.DOMAIN[resource]['cache_expires']
        else:
            cache_control = config.CACHE_CONTROL
            expires = config.CACHE_EXPIRES
        if cache_control:
            resp.headers.add('Cache-Control', cache_control)
        if expires:
            resp.expires = time.time() + expires

    # etag and last-modified
    if etag:
        resp.headers.add('ETag', etag)
    if last_modified:
        resp.headers.add('Last-Modified', date_to_str(last_modified))

    # CORS
    if 'Origin' in request.headers and config.X_DOMAINS is not None:
        if isinstance(config.X_DOMAINS, basestring):
            domains = [config.X_DOMAINS]
        else:
            domains = config.X_DOMAINS

        if config.X_HEADERS is None:
            headers = []
        elif isinstance(config.X_HEADERS, basestring):
            headers = [config.X_HEADERS]
        else:
            headers = config.X_HEADERS

        methods = app.make_default_options_response().headers['allow']
        resp.headers.add('Access-Control-Allow-Origin', ', '.join(domains))
        resp.headers.add('Access-Control-Allow-Headers', ', '.join(headers))
        resp.headers.add('Access-Control-Allow-Methods', methods)
        resp.headers.add('Access-Control-Allow-Max-Age', 21600)

    # Rate-Limiting
    limit = get_rate_limit()
    if limit and limit.send_x_headers:
        resp.headers.add('X-RateLimit-Remaining', str(limit.remaining))
        resp.headers.add('X-RateLimit-Limit', str(limit.limit))
        resp.headers.add('X-RateLimit-Reset', str(limit.reset))

    return resp
Example #6
0
def _prepare_response(resource,
                      dct,
                      last_modified=None,
                      etag=None,
                      status=200,
                      headers=None):
    """ Prepares the response object according to the client request and
    available renderers, making sure that all accessory directives (caching,
    etag, last-modified) are present.

    :param resource: the resource involved.
    :param dct: the dict that should be sent back as a response.
    :param last_modified: Last-Modified header value.
    :param etag: ETag header value.
    :param status: response status.

    .. versionchanged:: 0.7
       Add support for regexes in X_DOMAINS_RE. Closes #660, #974.
       ETag value now surrounded by double quotes. Closes #794.

    .. versionchanged:: 0.6
       JSONP Support.

    .. versionchanged:: 0.4
       Support for optional extra headers.
       Fix #381. 500 instead of 404 if CORS is enabled.

    .. versionchanged:: 0.3
       Support for X_MAX_AGE.

    .. versionchanged:: 0.1.0
       Support for optional HATEOAS.

    .. versionchanged:: 0.0.9
       Support for Python 3.3.

    .. versionchanged:: 0.0.7
       Support for Rate-Limiting.

    .. versionchanged:: 0.0.6
       Support for HEAD requests.

    .. versionchanged:: 0.0.5
       Support for Cross-Origin Resource Sharing (CORS).

    .. versionadded:: 0.0.4
    """
    if request.method == "OPTIONS":
        resp = app.make_default_options_response()
    else:
        # obtain the best match between client's request and available mime
        # types, along with the corresponding render function.
        mime, renderer_cls = _best_mime()

        # invoke the render function and obtain the corresponding rendered item
        rendered = renderer_cls().render(dct)

        # JSONP
        if config.JSONP_ARGUMENT:
            jsonp_arg = config.JSONP_ARGUMENT
            if jsonp_arg in request.args and "json" in mime:
                callback = request.args.get(jsonp_arg)
                rendered = "%s(%s)" % (callback, rendered)

        # build the main wsgi response object
        resp = make_response(rendered, status)
        resp.mimetype = mime

    # extra headers
    if headers:
        for header, value in headers:
            if header != "Content-Type":
                resp.headers.add(header, value)

    # cache directives
    if request.method in ("GET", "HEAD"):
        if resource:
            cache_control = config.DOMAIN[resource]["cache_control"]
            expires = config.DOMAIN[resource]["cache_expires"]
        else:
            cache_control = config.CACHE_CONTROL
            expires = config.CACHE_EXPIRES
        if cache_control:
            resp.headers.add("Cache-Control", cache_control)
        if expires:
            resp.expires = time.time() + expires

    # etag and last-modified
    if etag:
        resp.headers.add("ETag", '"' + etag + '"')
    if last_modified:
        resp.headers.add("Last-Modified", date_to_rfc1123(last_modified))

    # CORS
    origin = request.headers.get("Origin")
    if origin and (config.X_DOMAINS or config.X_DOMAINS_RE):
        if config.X_DOMAINS is None:
            domains = []
        elif isinstance(config.X_DOMAINS, str):
            domains = [config.X_DOMAINS]
        else:
            domains = config.X_DOMAINS

        if config.X_DOMAINS_RE is None:
            domains_re = []
        elif isinstance(config.X_DOMAINS_RE, str):
            domains_re = [config.X_DOMAINS_RE]
        else:
            domains_re = config.X_DOMAINS_RE

        # precompile regexes and ignore invalids
        domains_re_compiled = []
        for domain_re in domains_re:
            try:
                domains_re_compiled.append(re.compile(domain_re))
            except re.error:
                continue

        if config.X_HEADERS is None:
            headers = []
        elif isinstance(config.X_HEADERS, str):
            headers = [config.X_HEADERS]
        else:
            headers = config.X_HEADERS

        if config.X_EXPOSE_HEADERS is None:
            expose_headers = []
        elif isinstance(config.X_EXPOSE_HEADERS, str):
            expose_headers = [config.X_EXPOSE_HEADERS]
        else:
            expose_headers = config.X_EXPOSE_HEADERS

        # The only accepted value for Access-Control-Allow-Credentials header
        # is "true"
        allow_credentials = config.X_ALLOW_CREDENTIALS is True

        methods = app.make_default_options_response().headers.get("allow", "")

        if "*" in domains:
            resp.headers.add("Access-Control-Allow-Origin", origin)
            resp.headers.add("Vary", "Origin")
        elif any(origin == domain for domain in domains):
            resp.headers.add("Access-Control-Allow-Origin", origin)
        elif any(domain.match(origin) for domain in domains_re_compiled):
            resp.headers.add("Access-Control-Allow-Origin", origin)
        else:
            resp.headers.add("Access-Control-Allow-Origin", "")
        resp.headers.add("Access-Control-Allow-Headers", ", ".join(headers))
        resp.headers.add("Access-Control-Expose-Headers",
                         ", ".join(expose_headers))
        resp.headers.add("Access-Control-Allow-Methods", methods)
        resp.headers.add("Access-Control-Max-Age", config.X_MAX_AGE)
        if allow_credentials:
            resp.headers.add("Access-Control-Allow-Credentials", "true")

    # Rate-Limiting
    limit = get_rate_limit()
    if limit and limit.send_x_headers:
        resp.headers.add("X-RateLimit-Remaining", str(limit.remaining))
        resp.headers.add("X-RateLimit-Limit", str(limit.limit))
        resp.headers.add("X-RateLimit-Reset", str(limit.reset))

    return resp
Example #7
0
def _prepare_response(resource, dct, last_modified=None, etag=None, status=200, headers=None):
    """ Prepares the response object according to the client request and
    available renderers, making sure that all accessory directives (caching,
    etag, last-modified) are present.

    :param resource: the resource involved.
    :param dct: the dict that should be sent back as a response.
    :param last_modified: Last-Modified header value.
    :param etag: ETag header value.
    :param status: response status.

    .. versionchanged:: 0.6
       JSONP Support.

    .. versionchanged:: 0.4
       Support for optional extra headers.
       Fix #381. 500 instead of 404 if CORS is enabled.

    .. versionchanged:: 0.3
       Support for X_MAX_AGE.

    .. versionchanged:: 0.1.0
       Support for optional HATEOAS.

    .. versionchanged:: 0.0.9
       Support for Python 3.3.

    .. versionchanged:: 0.0.7
       Support for Rate-Limiting.

    .. versionchanged:: 0.0.6
       Support for HEAD requests.

    .. versionchanged:: 0.0.5
       Support for Cross-Origin Resource Sharing (CORS).

    .. versionadded:: 0.0.4
    """
    if request.method == "OPTIONS":
        resp = app.make_default_options_response()
    else:
        # obtain the best match between client's request and available mime
        # types, along with the corresponding render function.
        mime, renderer = _best_mime()

        # invoke the render function and obtain the corresponding rendered item
        rendered = globals()[renderer](dct)

        # JSONP
        if config.JSONP_ARGUMENT:
            jsonp_arg = config.JSONP_ARGUMENT
            if jsonp_arg in request.args and "json" in mime:
                callback = request.args.get(jsonp_arg)
                rendered = "%s(%s)" % (callback, rendered)

        # build the main wsgi rensponse object
        resp = make_response(rendered, status)
        resp.mimetype = mime

    # extra headers
    if headers:
        for header, value in headers:
            if header != "Content-Type":
                resp.headers.add(header, value)

    # cache directives
    if request.method in ("GET", "HEAD"):
        if resource:
            cache_control = config.DOMAIN[resource]["cache_control"]
            expires = config.DOMAIN[resource]["cache_expires"]
        else:
            cache_control = config.CACHE_CONTROL
            expires = config.CACHE_EXPIRES
        if cache_control:
            resp.headers.add("Cache-Control", cache_control)
        if expires:
            resp.expires = time.time() + expires

    # etag and last-modified
    if etag:
        resp.headers.add("ETag", etag)
    if last_modified:
        resp.headers.add("Last-Modified", date_to_rfc1123(last_modified))

    # CORS
    origin = request.headers.get("Origin")
    if origin and config.X_DOMAINS:
        if isinstance(config.X_DOMAINS, str):
            domains = [config.X_DOMAINS]
        else:
            domains = config.X_DOMAINS

        if config.X_HEADERS is None:
            headers = []
        elif isinstance(config.X_HEADERS, str):
            headers = [config.X_HEADERS]
        else:
            headers = config.X_HEADERS

        if config.X_EXPOSE_HEADERS is None:
            expose_headers = []
        elif isinstance(config.X_EXPOSE_HEADERS, str):
            expose_headers = [config.X_EXPOSE_HEADERS]
        else:
            expose_headers = config.X_EXPOSE_HEADERS

        # The only accepted value for Access-Control-Allow-Credentials header
        # is "true"
        allow_credentials = config.X_ALLOW_CREDENTIALS is True

        methods = app.make_default_options_response().headers.get("allow", "")

        if "*" in domains:
            resp.headers.add("Access-Control-Allow-Origin", origin)
            resp.headers.add("Vary", "Origin")
        elif origin in domains:
            resp.headers.add("Access-Control-Allow-Origin", origin)
        else:
            resp.headers.add("Access-Control-Allow-Origin", "")
        resp.headers.add("Access-Control-Allow-Headers", ", ".join(headers))
        resp.headers.add("Access-Control-Expose-Headers", ", ".join(expose_headers))
        resp.headers.add("Access-Control-Allow-Methods", methods)
        resp.headers.add("Access-Control-Allow-Max-Age", config.X_MAX_AGE)
        if allow_credentials:
            resp.headers.add("Access-Control-Allow-Credentials", "true")

    # Rate-Limiting
    limit = get_rate_limit()
    if limit and limit.send_x_headers:
        resp.headers.add("X-RateLimit-Remaining", str(limit.remaining))
        resp.headers.add("X-RateLimit-Limit", str(limit.limit))
        resp.headers.add("X-RateLimit-Reset", str(limit.reset))

    return resp