Request & Response

Access incoming request data and control outgoing responses through thread-safe proxy objects.

The Request Object

The request object is a thread-local proxy to the current BaseRequest. Import it from lcore:

from lcore import request

@app.route('/info')
def info():
    return {
        'method': request.method,        # 'GET', 'POST', etc.
        'path': request.path,            # '/info'
        'url': request.url,              # 'http://localhost:8080/info?q=test'
        'fullpath': request.fullpath,    # '/info' (with script_name)
        'ip': request.remote_addr,       # '127.0.0.1'
        'content_type': request.content_type,
        'content_length': request.content_length,
        'is_ajax': request.is_xhr,       # True if X-Requested-With
        'chunked': request.chunked,      # True if chunked encoding
    }

Request Properties

PropertyTypeDescription
methodstrHTTP method (GET, POST, etc.)
pathstrRequest path without query string
urlstrFull request URL including scheme, host, path, query
fullpathstrPath with script_name prefix
query_stringstrRaw query string
script_namestrWSGI SCRIPT_NAME
remote_addrstrClient IP address
remote_routelistList of IPs from X-Forwarded-For
content_typestrContent-Type header value
content_lengthintContent-Length (-1 if invalid)
is_xhrboolTrue if XMLHttpRequest
is_ajaxboolAlias for is_xhr
authtupleParsed Basic auth (username, password) or None
chunkedboolTrue if chunked transfer encoding

Query Parameters

# GET /search?q=python&page=2&tag=web&tag=api
@app.route('/search')
def search():
    q = request.query.get('q', '')          # 'python'
    page = request.query.get('page', '1')   # '2' (always strings)
    tags = request.query.getall('tag')       # ['web', 'api']
    return {'q': q, 'page': int(page), 'tags': tags}

request.query and request.GET are FormsDict instances. Use request.params to access combined query + form data.

Form Data

# POST with application/x-www-form-urlencoded
@app.post('/login')
def login():
    username = request.forms.get('username')
    password = request.forms.get('password')
    return {'user': username}

request.forms contains form fields from POST bodies. request.POST includes both form fields and file uploads from multipart requests.

JSON Body

# POST with application/json body
@app.post('/api/users')
def create_user():
    data = request.json  # Auto-parsed dict
    if data is None:
        abort(400, 'Invalid JSON')
    return {'created': data.get('name')}
Note

request.json returns None if the Content-Type is not application/json or the body cannot be parsed. Always check for None.

File Uploads

@app.post('/upload')
def upload():
    upload = request.files.get('document')
    if not upload:
        abort(400, 'No file uploaded')

    # FileUpload properties
    name = upload.filename        # Sanitized filename
    content_type = upload.content_type
    size = upload.content_length

    # Save to disk
    upload.save('/tmp/uploads/', overwrite=True)

    return {
        'filename': name,
        'content_type': content_type,
        'size': size
    }

FileUpload Object

Property/MethodDescription
.filenameSanitized filename (path components stripped)
.fileFile-like object for reading content
.content_typeMIME type from Content-Type header
.content_lengthFile size in bytes
.get_header(name)Get a specific multipart header
.save(dest, overwrite=False)Save file to directory or path

Cookies

Reading Cookies

@app.route('/read')
def read_cookies():
    # Plain cookies
    session_id = request.cookies.get('session_id', 'none')

    # Signed cookies (HMAC-SHA256 + JSON, tamper-proof)
    user = request.get_cookie('user', secret='my-secret-key')

    return {'session': session_id, 'user': user}

Setting Cookies

from datetime import timedelta

@app.route('/set')
def set_cookies():
    # Plain cookie
    response.set_cookie('session_id', 'abc123',
        path='/',
        httponly=True,
        secure=True,
        samesite='Lax'
    )

    # Signed cookie (tamper-proof)
    response.set_cookie('user', 'alice',
        secret='my-secret-key',
        max_age=timedelta(days=7)
    )

    # Delete a cookie
    response.delete_cookie('old_cookie')

    return 'Cookies set!'

Cookie Options

OptionTypeDescription
max_ageint / timedeltaCookie lifetime in seconds (timedelta auto-converted)
expiresdatetimeAbsolute expiry time
pathstrCookie path scope (default: current path)
domainstrCookie domain scope
secureboolOnly send over HTTPS
httponlyboolPrevent JavaScript access
samesitestrLax, Strict, or None
Signed Cookie Security

Signed cookies use HMAC-SHA256 with JSON serialization (not pickle). The signature prevents tampering, and JSON ensures safe deserialization. Always use a strong secret key.

Headers

Request Headers

@app.route('/headers')
def show_headers():
    # Case-insensitive access
    auth = request.headers.get('Authorization')
    ct = request.get_header('Content-Type', 'none')
    custom = request.headers.get('X-Custom-Header')
    return {'auth': auth, 'content_type': ct, 'custom': custom}

Response Headers

@app.route('/api/data')
def data():
    response.set_header('X-Request-ID', 'abc-123')
    response.set_header('Cache-Control', 'max-age=3600')
    response.add_header('Set-Cookie', 'a=1')  # add_header allows duplicates
    response.content_type = 'application/json'
    return {'data': 'value'}
MethodDescription
response.set_header(name, value)Set header (replaces existing)
response.add_header(name, value)Add header (allows multiple values)
response.get_header(name)Read a response header

The Response Object

from lcore import response

@app.route('/custom')
def custom_response():
    response.status = 201
    response.content_type = 'application/json'
    response.set_header('X-Custom', 'value')
    return {'status': 'created'}

Response Properties

PropertyTypeDescription
statusint / strSet with int (200) or string ('200 OK')
status_codeintNumeric status code (read-only)
status_linestrFull status line (read-only)
content_typestrContent-Type header (default: text/html; charset=UTF-8)
content_lengthintContent-Length header
charsetstrCharacter set from Content-Type
expiresdatetimeExpires header
headersHeaderDictMutable header dictionary
bodystr/bytesResponse body

HTTPResponse for Immediate Return

from lcore import HTTPResponse

@app.route('/immediate')
def immediate():
    # Raise HTTPResponse to skip normal processing
    raise HTTPResponse(
        body='{"error": "custom"}',
        status=418,
        headers={'Content-Type': 'application/json'}
    )

Static Files

from lcore import static_file

@app.route('/static/<filepath:path>')
def serve_static(filepath):
    return static_file(filepath, root='./public')

# With download mode
@app.route('/download/<filename>')
def download(filename):
    return static_file(filename, root='./files', download=True)

# Custom headers and mimetype
@app.route('/assets/<path:path>')
def assets(path):
    return static_file(path, root='./assets',
        headers={'Cache-Control': 'max-age=86400'}
    )

static_file Parameters

ParameterTypeDescription
filenamestrRelative file path
rootstrRoot directory to serve from
mimetypestr/boolOverride MIME type (True = auto-detect)
downloadbool/strTrue or filename for Content-Disposition
charsetstrCharacter set for text files (default: UTF-8)
etagstrCustom ETag (default: SHA256 auto-generated)
headersdictAdditional response headers
Security

static_file uses os.path.realpath() to resolve symlinks and prevent path traversal attacks. Download filenames are sanitized with os.path.basename(). ETags use SHA256.

Redirects

from lcore import redirect

@app.route('/old-page')
def old_page():
    redirect('/new-page')           # 303 See Other (default)

@app.route('/moved')
def moved():
    redirect('/new-location', 301)  # Permanent redirect

@app.route('/login-required')
def login_required():
    redirect('/login', 307)         # Temporary, preserves method

Redirect Codes

CodeNameUse Case
301Moved PermanentlyURL has permanently changed (SEO)
302FoundTemporary redirect (legacy)
303See OtherRedirect after POST (default)
307Temporary RedirectPreserves HTTP method
308Permanent RedirectPermanent, preserves method