# Request and response objects

Reading input, building responses and channel-specific metadata in every service.

Every service invocation comes with two objects - `self.request` is what the service received and `self.response` is what it sends back. Both always exist and both can be empty, it is perfectly fine for a service to accept no input and to produce no output.

**Request**

## Overview {#overview}

`self.request.input` is the incoming message, parsed - a dict-like object for JSON, the raw text for other formats. `self.request.raw` is the message exactly as it arrived on the wire.

Read `input` to work with the data and read `raw` when you need the exact bytes as received, e.g. for checksums or signatures.

Both attributes work the same on every channel - REST, AMQP, Kafka, IBM MQ, the scheduler or any other. Channel-specific details, such as HTTP headers or AMQP delivery metadata, have their own sections further down.

## Reading input {#reading-input}

On JSON channels, `self.request.input` is the parsed request. Both dot access and dict access work:

```python
# -*- coding: utf-8 -*-

# Zato
from zato.server.service import Service

class CreateOrder(Service):

    def handle(self):
        customer_id = self.request.input.customer_id
        quantity = self.request.input['quantity']
```

```bash
curl -X POST http://localhost:11223/api/orders \
  -d '{"customer_id":"C-1001", "quantity":3}'
```

URL query string and path parameters are merged into `input` as well:

```python
class GetOrder(Service):

    def handle(self):
        order_id = self.request.input.order_id # From the URL path
        region = self.request.input.region     # From the query string
```

```bash
# The channel's URL path is /api/orders/{order_id}
curl "http://localhost:11223/api/orders/ORD-123?region=north"
```

What happens if a key does not exist? Reading it raises an `AttributeError` at the very line that reads it, naming both the missing key and the keys that do exist:

```text
AttributeError: No such key `city` among `["customer_id", "quantity"]`
```

To read a key that may or may not be there, check for it first:

```python
def handle(self):
    if 'priority' in self.request.input:
        priority = self.request.input.priority
```

On non-JSON channels, such as EDIFACT or HL7 ones, `input` is the incoming text exactly as received:

```python
class ProcessInterchange(Service):

    def handle(self):
        # 'UNB+UNOC:3+SENDER+RECIPIENT+260721:0130+REF-0001'...
        interchange = self.request.input
```

For EDIFACT, `self.request.edifact` is the same interchange already parsed into typed messages. It reads the text from wherever the channel put it - the first plain-text part of an e-mail, the bytes of a received file, the body of a REST request, the operation element of a SOAP request or a queue message - so one service works the same behind any of them:

```python
class ProcessInterchange(Service):

    def handle(self):
        interchange = self.request.edifact
        sender = interchange.header.sender.identification
```

> **Try it:** Run the first curl call above against your own channel, then remove the quantity field from the JSON and read it in the service anyway - what error do you get back and which line does it point to?

## Declared input {#declared-input}

Input can also be declared up front. A declaration means that:

- The declared names are parsed into `input` before `handle` runs
- Required names are enforced - a request without one is rejected with an error naming the missing element
- Reading an optional name that was not sent gives `None`
- The declarations feed the [OpenAPI documentation](https://zato.io/docs/dev/rest/openapi.html) generated for the service

A declaration is a list of names and a leading minus means the name is optional:

```python
class GetProfile(Service):

    input = 'customer_id', '-priority'

    def handle(self):
        customer_id = self.request.input.customer_id
        priority = self.request.input.priority # None when not sent
```

```bash
curl "http://localhost:11223/api/profile?customer_id=C-1001"
```

When a required name is missing, the request never reaches `handle` - it is rejected with an error naming the element, e.g. `Missing required input element: customer_id`.

The full rules - the types a name implies, output declarations and models side by side - are on the [declaring input and output](https://zato.io/docs/dev/input-output.html) page.

Input can also be a [data model](https://zato.io/docs/dev/model/index.html), in which case `input` is an instance of that model:

```python
# -*- coding: utf-8 -*-

# stdlib
from dataclasses import dataclass

# Zato
from zato.common.marshal_.api import Model
from zato.server.service import Service

@dataclass(init=False)
class CreateOrderRequest(Model):
    customer_id: str
    quantity: int

class CreateOrder(Service):

    input = CreateOrderRequest

    def handle(self):
        # self.request.input is a CreateOrderRequest instance
        customer_id = self.request.input.customer_id
```

> **Try it:** Invoke GetProfile without the customer\_id parameter - what does the caller receive and what does the server log say?

## Raw requests {#raw-requests}

`self.request.raw` is the message exactly as received, str or bytes, before any parsing. Signature verification and checksums must use `raw`, never `input` - `input` is the parsed form and re-serializing it will not reproduce the original bytes:

```python
class ProcessWebhook(Service):

    def handle(self):
        signature = self.request.http.headers['x-signature']
        raw_body = self.request.raw

        # Compute the HMAC over raw_body and compare it with the signature
```

The [webhooks chapter](https://zato.io/docs/dev/rest/webhooks.html) shows a complete signature verification example.

## Channel context - HTTP {#channel-context-http}

Services invoked over HTTP receive their channel context through `self.request.http`. Each capability below is available on any HTTP channel.

Read a query string parameter:

```python
def handle(self):
    region = self.request.http.GET.region
```

```bash
curl "http://localhost:11223/api/orders?region=north"
```

Read a path parameter - for a channel whose URL path is `/api/orders/{order_id}`:

```python
def handle(self):
    order_id = self.request.http.params.order_id
```

```bash
curl http://localhost:11223/api/orders/ORD-123
```

Read the HTTP method, e.g. to serve GET and POST from one service:

```python
def handle(self):
    if self.request.http.method == 'GET':
        self.response.payload.action = 'read'
    else:
        self.response.payload.action = 'write'
```

Read a header - header names are lower-cased and use dashes:

```python
def handle(self):
    api_key = self.request.http.headers['x-api-key']
```

```bash
curl -H "X-API-Key: abc" http://localhost:11223/api/orders
```

Read form data from a multipart request:

```python
def handle(self):
    form = self.request.http.get_form_data()
    file_name = form['file_name']
```

## Channel context - AMQP {#channel-context-amqp}

Services invoked over AMQP receive their channel context through `self.request.amqp`. The attribute holds the delivery itself and lets the service acknowledge or reject it:

```python
class ProcessDelivery(Service):

    def handle(self):
        amqp = self.request.amqp

        # The message as delivered by the broker
        routing_key = amqp.msg.delivery_info['routing_key']
        app_headers = amqp.msg.headers

        # Acknowledge or reject the delivery explicitly ..
        amqp.ack()

        # .. or amqp.reject() to send it back.
```

Calling `ack` or `reject` is optional - when the service ends without either, the channel applies its own acknowledgment mode automatically. Call them yourself only when the decision has to be made mid-flight.

## Channel context - queue bridge headers {#channel-context-queue-bridge-headers}

Services invoked through queue bridge channels - Kafka and IBM MQ - receive broker metadata through `self.request.headers`, a plain dict. For IBM MQ these are the MQMD and MQRFH2 fields. One common use is routing on a header:

```python
class RouteMessage(Service):

    def handle(self):
        message_type = self.request.headers['message_type']

        if message_type == 'order':
            self.invoke('orders.process', self.request.input)
        else:
            self.invoke('audit.store', self.request.input)
```

## Channel context - SOAP {#channel-context-soap}

On SOAP channels the operation element arrives pre-unwrapped - `input` holds the operation's content and no envelope handling is needed in services. The [SOAP tutorial](https://zato.io/tutorials/soap/receiving.html) covers it end to end.

## Reference {#reference}

Attributes available to all services, regardless of the channel:

| Attribute | Description |
| --- | --- |
| self.request | Along with self.channel, one of the main attributes describing incoming messages |
| self.request.input | The incoming message, parsed - see [Reading input](https://zato.io/docs/dev/request-response/index.html#reading-input) and [Declared input](https://zato.io/docs/dev/request-response/index.html#declared-input) |
| self.request.raw | The message exactly as received, str or bytes - see [Raw requests](https://zato.io/docs/dev/request-response/index.html#raw-requests) |
| self.request.edifact | The incoming EDIFACT interchange, parsed on first access - see [EDIFACT](https://zato.io/docs/dev/healthcare/edifact/index.html) |
| self.request.cid | Correlation ID for the current request - useful for logging and tracing |
| self.request.data\_format | Data format of the request, e.g. 'json' or 'xml' |
| self.request.transport | Transport type used for the request |
| self.channel | Along with self.request, describes data and metadata about incoming messages. Unlike self.request, this attribute is the same for all requests coming through the same channel, i.e. it describes details of the channel itself rather than each individual message received. |
| self.channel.id | Unique ID of the channel |
| self.channel.name | Name of the channel the request was received through |
| self.channel.type | Type of the channel - will be equal to one of the constants in zato.common.CHANNEL |
| self.chan | Alias to self.channel |
| self.channel.security | Describes a security definition attached to the channel, if any is at all |
| self.channel.security.name | Name of the security definition |
| self.channel.security.username | Username used to invoke the channel, if applicable for a particular security type |
| self.channel.security.type | Type of the security definition - will be equal to one of the constants in zato.common.SEC\_DEF\_TYPE |
| self.channel.sec | Alias to self.channel.security |

HTTP-specific attributes, each shown in an example in [Channel context - HTTP](https://zato.io/docs/dev/request-response/index.html#channel-context-http):

| Attribute | Description |
| --- | --- |
| self.request.http | The attribute to use to access HTTP-specific information |
| self.request.http.method | HTTP method used to invoke the service |
| self.request.http.GET | All GET parameters as a Bunch object, each value is either an exact one received or a list of values if there was more than one for a given key |
| self.request.http.POST | All POST parameters as a Bunch object, each value is either an exact one received or a list of values if there was more than one for a given key. Populated for channels whose data format is form data and for channels with no data format at all. |
| self.request.http.path | URL path that the request was received through, e.g. */customer/123* in "https://localhost:17010/customer/123"; the value does not include a query string |
| self.request.http.params | A dict-like object with URL path parameters, e.g. the value of {customer\_id} from a channel mounted at /api/customers/{customer\_id}. Query string parameters are in self.request.http.GET. Available even if [data models](https://zato.io/docs/dev/model/index.html) are not used. |
| self.request.http.headers | All HTTP headers as a Bunch object, names lower-cased and dash-joined, e.g. 'x-api-key' |
| self.request.http.user\_agent | The User-Agent header from the HTTP request |
| self.request.http.get\_form\_data() | Returns form data from multipart requests as a dictionary |

## More information {#more-information}

- Consult the [dedicated chapter](https://zato.io/docs/dev/examples/index.html) with programming examples for more details.
- The [message building examples](https://zato.io/docs/dev/examples/messages.html) show the dot-access pattern in full
- To learn more about data models, click [here](https://zato.io/docs/dev/model/index.html)

**Response**

## Overview {#overview_1}

All services produce responses through `self.response.payload`. What you assign determines what is sent to the caller:

1. Dot access on the payload builds a nested structure of any depth and it serializes to the channel's data format
2. A dict replaces anything built so far and serializes as it is
3. A list of dicts becomes an array on the wire
4. A string passes through to the wire exactly as it is
5. A [model instance](https://zato.io/docs/dev/model/index.html) serializes from its declared fields
6. A payload that was never assigned nor built produces an empty response body

Output can also be declared, either as a list of names or as a [data model](https://zato.io/docs/dev/model/index.html), and the payload then follows the declared structure - the sections below cover each case. The [message building examples](https://zato.io/docs/dev/examples/messages.html) show the dot-access pattern in full.

## Free-form responses {#free-form-responses}

With zero declarations, dot access on the payload builds nested structures of any depth and the assignments serialize to JSON in the order they were made:

```python
def handle(self):
    self.response.payload.customer.name = 'John Doe'
    self.response.payload.customer.address.city = 'Amsterdam'
```

```json
{"customer": {"name": "John Doe", "address": {"city": "Amsterdam"}}}
```

A list can be assigned under any nested name too:

```python
def handle(self):
    self.response.payload.order.lines = [
        {'sku': 'AB-12', 'quantity': 2},
        {'sku': 'CD-34', 'quantity': 1},
    ]
```

```json
{"order": {"lines": [{"sku": "AB-12", "quantity": 2}, {"sku": "CD-34", "quantity": 1}]}}
```

A dict can be assigned as a whole and it replaces anything built so far:

```python
def handle(self):
    self.response.payload = {'id':123, 'name':'John Doe'}
```

The same goes for a list of objects:

```python
def handle(self):
    data = [
        {'id':123, 'name': 'John Doe'},
        {'id':456, 'name': 'Jane Xi'},
    ]
    self.response.payload = data
```

Assigning a string directly is always possible too, e.g. as a result of manual serialization - strings pass through to the wire exactly as they are:

```python
def handle(self):
    self.response.payload = '{"id":123, "name":"John Doe"}'
```

A payload that was never assigned nor built produces an empty response body - a service with an empty `handle` returns nothing rather than an empty JSON object.

> **Try it:** Build the nested customer response above in a service of your own, then add one more assignment below the existing two - does the order of keys in the JSON output match the order of your assignments?

## Declared output names {#declared-output-names}

Declaring output names makes the payload accept those names only - assigning any other name raises an error at the very line that assigns it, naming both the unknown name and the declared list, so typos never reach the wire. Below each declared name, any nested structure is allowed and is built through the same dot access:

```python
class GetCustomer(Service):

    output = 'customer'

    def handle(self):
        self.response.payload.customer.name = 'John Doe'
        self.response.payload.customer.address.city = 'Amsterdam'

        # This would raise an error - 'status' is not among the declared names
        # self.response.payload.status = 'active'
```

A dict assigned as a whole keeps the declared names only - anything else in the dict is dropped, so an internal dict can be assigned as the payload and its undeclared keys never reach the caller:

```python
class GetOrder(Service):

    output = 'customer_id', 'status'

    def handle(self):
        data = {'customer_id': 'C-1001', 'status': 'confirmed', 'internal_note': 'not for the wire'}
        self.response.payload = data

        # The response is {"customer_id": "C-1001", "status": "confirmed"}
```

For responses that are lists, build them through `append`:

```python
class GetOrders(Service):

    output = 'customer_id'

    def handle(self):
        self.response.payload.append({'customer_id': 'C-1001'})
        self.response.payload.append({'customer_id': 'C-1002'})

        # The response is [{"customer_id": "C-1001"}, {"customer_id": "C-1002"}]
```

## Model responses {#model-responses}

Declaring a [data model](https://zato.io/docs/dev/model/index.html) as output fixes the whole structure of the response - the payload is an instance of the model and only the model's fields can be assigned:

```python
@dataclass(init=False)
class GetCustomerResponse(Model):
    name: str
    status: str

class GetCustomer(Service):

    output = GetCustomerResponse

    def handle(self):
        self.response.payload.name = 'John Doe'
        self.response.payload.status = 'active'

        # This would raise an AttributeError - there is no such field in the model
        # self.response.payload.address = 'Main Street 123'
```

A model instance built elsewhere - in a helper method, another module or a mapping layer - can be assigned to the payload as a whole and it serializes from its fields:

```python
class GetCustomer(Service):

    output = GetCustomerResponse

    def build_response(self):
        response = GetCustomerResponse()
        response.name = 'John Doe'
        response.status = 'active'
        return response

    def handle(self):
        self.response.payload = self.build_response()
```

> **Try it:** Uncomment the address line in the GetCustomer example - which line does the error point to and what does it say?

## Reference {#reference_1}

All of the attributes and methods are always available to all services, regardless of the protocol they are invoked through though in the case of HTTP-specific ones, using them will be a no-op if the service is not invoked through HTTP.

| Attribute | Description |
| --- | --- |
| self.response | The main attribute via which responses are produced |
| self.response.payload | The object to which responses are assigned, i.e. this is the attribute through which a service's business data is returned, such as a JSON message |
| self.response.status\_code | *(HTTP only)* An integer status code such as 200 or 401 to return in response |
| self.response.content\_type | *(HTTP only)* Sets response's Content-Type header value |
| self.response.headers | *(HTTP only)* A dictionary of header name/value to set in the response |

## More information {#more-information_1}

- Consult the [dedicated chapter](https://zato.io/docs/dev/examples/index.html) with programming examples for more details.
- The [message building examples](https://zato.io/docs/dev/examples/messages.html) show the dot-access pattern in full
- To learn more about data models, click [here](https://zato.io/docs/dev/model/index.html)
