Skip to content
This repository has been archived by the owner on Oct 25, 2022. It is now read-only.

Response content handling

brunodecarvalho edited this page Feb 7, 2013 · 1 revision

In-depth guide to response content handling

Introduction

Out of the box, hotpotato can automatically convert responses to strings, JSON or images, as well as pipe the data directly to an output stream or download to a file.

The response handling behavior for each request can be configured by assigning a response content handler to each request instance (via responseContentHandler property).

If the request has no handler configured when it is executed, it will discard the response body. Otherwise, the result will be available at the content property of the resulting BBHTTPResponse.

Determining whether or not to parse a response body

Sadly, not all requests end well. If you're trying to retrieve a JSON resource and the server responds with a 400 response code, the response body, if available, should not be parsed; and by default, it isn't. All the response content handlers available with hotpotato inherit behavior from a selective response content handler — BBHTTPSelectiveDiscarder.

This handler (and by consequence, all its subclasses) has two properties you can use to decide whether the content should be accepted and parsed:

  • acceptableResponses: array of NSNumber, contains the response codes that hotpotato will consider acceptable for decoding to happen automatically. When set to an empty array (or nil), any response code will be accepted.

  • acceptableContentTypes: array of NSString, contains the content types (value of the Content-Type header of the response) that hotpotato will consider acceptable for decoding to happen automatically. When set to an empty array (or nil), any content type will be accepted. When the response has no Content-Type header (or content), conversion will fail.

Note: Some implementations have static methods to affect defaults:

  • setDefaultAcceptableResponses:
  • setDefaultAcceptableContentTypes:

These will affect all newly created handler instances. Might come in handy if the defaults don't suit your needs.

As to actually handling the content, this semi-abstract implementation merely discards everything it receives.

Available behaviors

Discard response content

  • Implementation: BBHTTPSelectiveDiscarder
  • Description: Ignores response content.
  • Acceptable response codes: 200, 201, 202, 203.
  • Acceptable content types: anything.
  • On success, object at response.content : Always nil.

You can call the method discardResponseContent on the request to have hotpotato set this behavior for you.

Given that this is a completely stateless implementation, if you ever need to use it directly, please consider using the singleton (sharedDiscarder class method).

Pack everything as a byte array

  • Implementation: BBHTTPAccumulator, extends BBHTTPSelectiveDiscarder.
  • Description: Accumulates all the data received for the response body and makes it available as a NSData.
  • Acceptable response codes: 200, 201, 202, 203.
  • Acceptable content types: anything.
  • On success, object at response.content : NSData

This is the default behavior for all requests so you don't have to do any setup. Still, if you explicitly wish to do so you can call the method downloadContentAsData on the request to have hotpotato set this behavior for you. You can also use the fluent/chained syntax by calling the asData method.

Convert to string

  • Implementation: BBHTTPToStringConverter, extends BBHTTPAccumulator.
  • Description: Converts all the data received for the response body and creates a new NSString with UTF-8 encoding.
  • Acceptable response codes: 200, 201, 202, 203.
  • Acceptable content types: anything.
  • On success, object at response.content : NSString

You can call the method downloadContentAsString on the request to have hotpotato set this behavior for you. You can also use the fluent/chained syntax by calling the asString method.

Parse JSON

  • Description: Converts the response body data to a JSON object. If the JSON object is a NSDictionary, it wraps that dictionary with BBJSONDictionary, which allows the keyed subscript operator to be used as an alias for valueForKeyPath: (the default would be valueForKey:).
  • Implementation: BBJSONParser (extends BBHTTPAccumulator)
  • Acceptable response codes: 200, 201, 202, 203.
  • Acceptable content types: application/json.
  • On success, object at response.content : A NSArray or a BBJSONDictionary.

You can call the method downloadContentAsJSON on the request to have hotpotato set this behavior for you. You can also use the fluent/chained syntax by calling the asJSON method.

Decode images

  • Implementation: BBHTTPImageDecoder, extends BBHTTPAccumulator.
  • Description: Create a UIImage (NSImage on OSX) using the contents of the response body.
  • Acceptable response codes: 200, 201, 202, 203.
  • Acceptable content types: image/*.
  • On success, object at response.content : UIImage on iOS, NSImage on OSX.

You can call the method downloadContentAsImage on the request to have hotpotato set this behavior for you. You can also use the fluent/chained syntax by calling the asImage method.

Stream to an output stream

  • Implementation: BBHTTPStreamWriter, extends BBHTTPSelectiveDiscarder.
  • Description: Pipes all bytes received, as they are received, to an output stream.
  • Acceptable response codes: 200, 201, 202, 203.
  • Acceptable content types: anything.
  • On success, object at response.content : Always nil.

You can call the method downloadToStream: and pass a NSOutputStream to have hotpotato set this behavior for you.

Download to disk

  • Implementation: BBHTTPFileWriter, extends BBHTTPSelectiveDiscarder.
  • Description: Streams the download to disk, writing bytes as they arrive — thus keeping memory usage to a minimum. If something goes wrong while downloading the file, the partial file is automatically cleaned up.
  • Acceptable response codes: 200, 201, 202, 203.
  • Acceptable content types: anything.
  • On success, object at response.content : Always nil.

You can call the method downloadToFile: and pass a NSString with the target location for the file, to have hotpotato set this behavior for you.

Implementing your own response content handler

As helpful as all the available behaviors may be, there will be cases where you need to tailor response handling to your specific needs.

If this is the case, you'll need to implement the protocol BBHTTPContentHandler and then supply an instance of your implementation to the request, via the responseContentHandler property.

Content handlers will be progressively fed with response body data and, when the response is finished, their parseContent: method will be called. This means you can adopt a progressive decoding approach and have parseContent: merely finish the process or accumulate data and then convert it when parseContent: is called.

Reusable, extensible handlers

  • If your handler converts JSON to a domain object, consider subclassing BBJSONParser;
  • If your handler first accumulates data into a buffer and then converts that buffer to a resulting object, consider subclassing BBHTTPAccumulator;
  • If your handler should only attempt to handle responses with certain response status codes or with certain content types, consider subclassing BBHTTPSelectiveDiscarder.

Also, don't forget to implement the optional cleanup method if your handler requires cleanup after either a successful or error response.

A note on converting content

If something goes wrong while performing the final conversion — triggered when hotpotato calls parseContent: — you must set the error method argument:

if (somethingWentWrong) *error = [NSError errorWith...];

Tip: You don't need to check whether error is NULL because hotpotato will always pass a valid address for you to set.

If the conversion involves multiple steps and it fails midway, when appropriate, you should return the intermediate object. For instance, when parsing JSON, BBJSONParser first closes the stream where it received all the data and obtains a NSData, which it then converts to a JSON object. If the conversion to a JSON object fails, it sets the appropriate error and returns the intermediate NSData — it may be useful for debug.