Skip to content

Commit

Permalink
Status code (TDStatus) overhaul
Browse files Browse the repository at this point in the history
* Use symbolic constants for codes, i.e. kTDStatusNotFound instead of 404.
* Add extra, more detailed status codes and map them to custom response messages.
  • Loading branch information
snej committed Apr 6, 2012
1 parent be19f46 commit 7f9ff78
Show file tree
Hide file tree
Showing 30 changed files with 506 additions and 399 deletions.
17 changes: 6 additions & 11 deletions Listener/TDHTTPResponse.m
Original file line number Diff line number Diff line change
Expand Up @@ -195,26 +195,21 @@ - (void) onFinished {
// to massage the response:
LogTo(TDListenerVerbose, @"%@ prettifying response body", self);
int status = _response.status;
if (status >= 300 && _data.length == 0) {
// Put a generic error message in the body:
if (TDStatusIsError(status) && _data.length == 0) {
// Put an error message in the body:
NSString* errorMsg;
switch (status) {
case 404: errorMsg = @"not_found"; break;
// TODO: There are more of these to add; see error_info() in couch_httpd.erl
default:
errorMsg = [NSHTTPURLResponse localizedStringForStatusCode: status];
}
status = _response.status = TDStatusToHTTPStatus(status, &errorMsg);
NSString* responseStr = [NSString stringWithFormat: @"{\"status\": %i, \"error\":\"%@\"}\n",
status, errorMsg];
[_response.headers setObject: @"text/plain; encoding=UTF-8" forKey: @"Content-Type"];
[self onDataAvailable: [responseStr dataUsingEncoding: NSUTF8StringEncoding]
finished: NO];
} else {
#if DEBUG
#if DEBUG
BOOL pretty = YES;
#else
#else
BOOL pretty = [_router boolQuery: @"pretty"];
#endif
#endif
if (pretty) {
[_data release];
_data = [_response.body.asPrettyJSON mutableCopy];
Expand Down
3 changes: 2 additions & 1 deletion Source/ChangeTracker/TDChangeTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#import "TDConnectionChangeTracker.h"
#import "TDSocketChangeTracker.h"
#import "TDMisc.h"
#import "TDStatus.h"


@interface TDChangeTracker ()
Expand Down Expand Up @@ -118,7 +119,7 @@ - (NSURLCredential*) authCredential {

- (void) setUpstreamError: (NSString*)message {
Warn(@"%@: Server error: %@", self, message);
self.error = [NSError errorWithDomain: @"TDChangeTracker" code: 502 userInfo: nil];
self.error = [NSError errorWithDomain: @"TDChangeTracker" code: kTDStatusUpstreamError userInfo: nil];
}

- (BOOL) start {
Expand Down
7 changes: 4 additions & 3 deletions Source/ChangeTracker/TDConnectionChangeTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#import "TDConnectionChangeTracker.h"
#import "TDMisc.h"
#import "TDStatus.h"


@implementation TDConnectionChangeTracker
Expand Down Expand Up @@ -79,11 +80,11 @@ - (void)connection:(NSURLConnection *)connection
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
int status = (int) ((NSHTTPURLResponse*)response).statusCode;
TDStatus status = (TDStatus) ((NSHTTPURLResponse*)response).statusCode;
LogTo(ChangeTracker, @"%@: Got response, status %d", self, status);
if (status >= 300) {
if (TDStatusIsError(status)) {
Warn(@"%@: Got status %i", self, status);
self.error = TDHTTPError(status, self.changesFeedURL);
self.error = TDStatusToNSError(status, self.changesFeedURL);
[self stop];
}
}
Expand Down
2 changes: 1 addition & 1 deletion Source/ChangeTracker/TDRemoteRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ typedef void (^TDRemoteRequestCompletionBlock)(id result, NSError* error);
- (id) initWithMethod: (NSString*)method URL: (NSURL*)url body: (id)body
onCompletion: (TDRemoteRequestCompletionBlock)onCompletion;

/** In some cases a 404 Not Found is an expected condition and shouldn't be logged; call this to suppress that log message. */
/** In some cases a kTDStatusNotFound Not Found is an expected condition and shouldn't be logged; call this to suppress that log message. */
- (void) dontLog404;

// protected:
Expand Down
9 changes: 5 additions & 4 deletions Source/ChangeTracker/TDRemoteRequest.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#import "TDMisc.h"
#import "TDMultipartReader.h"
#import "TDBlobStore.h"
#import "TDDatabase.h"


// Max number of retry attempts for a transient failure
Expand Down Expand Up @@ -100,7 +101,7 @@ - (void) cancelWithStatus: (int)status {
return;
}

[self connection: _connection didFailWithError: TDHTTPError(status, _request.URL)];
[self connection: _connection didFailWithError: TDStatusToNSError(status, _request.URL)];
}


Expand All @@ -110,7 +111,7 @@ - (void) cancelWithStatus: (int)status {
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
int status = (int) ((NSHTTPURLResponse*)response).statusCode;
LogTo(RemoteRequest, @"%@: Got response, status %d", self, status);
if (status >= 300)
if (TDStatusIsError(status))
[self cancelWithStatus: status];
}

Expand All @@ -120,7 +121,7 @@ - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (WillLog()) {
if (!(_dontLog404 && error.code == 404 && $equal(error.domain, TDHTTPErrorDomain)))
if (!(_dontLog404 && error.code == kTDStatusNotFound && $equal(error.domain, TDHTTPErrorDomain)))
Log(@"%@: Got error %@", self, error);
}
[self clearConnection];
Expand Down Expand Up @@ -174,7 +175,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (!result) {
Warn(@"%@: %@ %@ returned unparseable data '%@'",
self, _request.HTTPMethod, _request.URL, [_jsonBuffer my_UTF8ToString]);
error = TDHTTPError(502, _request.URL);
error = TDStatusToNSError(kTDStatusUpstreamError, _request.URL);
}
[self clearConnection];
[self respondWithResult: result error: error];
Expand Down
75 changes: 37 additions & 38 deletions Source/TDDatabase+Attachments.m
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ - (TDStatus) insertAttachment: (TDAttachment*)attachment
attachment.contentType, $object(attachment->encoding),
$object(attachment->length), encodedLengthObj,
$object(attachment->revpos)]) {
return 500;
return kTDStatusDBError;
}
return 201;
return kTDStatusCreated;
}


Expand All @@ -107,23 +107,23 @@ - (TDStatus) copyAttachmentNamed: (NSString*)name
Assert(toSequence > 0);
Assert(toSequence > fromSequence);
if (fromSequence <= 0)
return 404;
return kTDStatusNotFound;
if (![_fmdb executeUpdate: @"INSERT INTO attachments "
"(sequence, filename, key, type, encoding, encoded_Length, length, revpos) "
"SELECT ?, ?, key, type, encoding, encoded_Length, length, revpos "
"FROM attachments WHERE sequence=? AND filename=?",
$object(toSequence), name,
$object(fromSequence), name]) {
return 500;
return kTDStatusDBError;
}
if (_fmdb.changes == 0) {
// Oops. This means a glitch in our attachment-management or pull code,
// or else a bug in the upstream server.
Warn(@"Can't find inherited attachment '%@' from seq#%lld to copy to #%lld",
name, fromSequence, toSequence);
return 404; // Fail if there is no such attachment on fromSequence
return kTDStatusNotFound; // Fail if there is no such attachment on fromSequence
}
return 200;
return kTDStatusOK;
}


Expand All @@ -150,29 +150,32 @@ - (NSData*) getAttachmentForSequence: (SequenceNumber)sequence
Assert(sequence > 0);
Assert(filename);
NSData* contents = nil;
*outStatus = 500;
FMResultSet* r = [_fmdb executeQuery:
@"SELECT key, type, encoding FROM attachments WHERE sequence=? AND filename=?",
$object(sequence), filename];
if (!r)
if (!r) {
*outStatus = kTDStatusDBError;
return nil;
}
@try {
if (![r next]) {
*outStatus = 404;
*outStatus = kTDStatusNotFound;
return nil;
}
NSData* keyData = [r dataNoCopyForColumnIndex: 0];
if (keyData.length != sizeof(TDBlobKey)) {
Warn(@"%@: Attachment %lld.'%@' has bogus key size %d",
self, sequence, filename, keyData.length);
*outStatus = kTDStatusCorruptError;
return nil;
}
contents = [_attachments blobForKey: *(TDBlobKey*)keyData.bytes];
if (!contents) {
Warn(@"%@: Failed to load attachment %lld.'%@'", self, sequence, filename);
*outStatus = kTDStatusCorruptError;
return nil;
}
*outStatus = 200;
*outStatus = kTDStatusOK;
if (outType)
*outType = [r stringForColumnIndex: 1];

Expand Down Expand Up @@ -320,35 +323,31 @@ - (NSDictionary*) attachmentsFromRevision: (TDRevision*)rev
// If there are no attachments in the new rev, there's nothing to do:
NSDictionary* revAttachments = [rev.properties objectForKey: @"_attachments"];
if (revAttachments.count == 0 || rev.deleted) {
*outStatus = 200;
*outStatus = kTDStatusOK;
return [NSDictionary dictionary];
}

TDStatus status = 200;
TDStatus status = kTDStatusOK;
NSMutableDictionary* attachments = $mdict();
for (NSString* name in revAttachments) {
// Create a TDAttachment object:
NSDictionary* attachInfo = [revAttachments objectForKey: name];
NSString* contentType = $castIf(NSString, [attachInfo objectForKey: @"content_type"]);
TDAttachment* attachment = [[[TDAttachment alloc] initWithName: name
contentType: contentType] autorelease];
if (!attachment) {
status = 500;
break;
}

NSString* newContentsBase64 = $castIf(NSString, [attachInfo objectForKey: @"data"]);
if (newContentsBase64) {
// If there's inline attachment data, decode and store it:
@autoreleasepool {
NSData* newContents = [TDBase64 decode: newContentsBase64];
if (!newContents) {
status = 400;
status = kTDStatusBadEncoding;
break;
}
attachment->length = newContents.length;
if (![self storeBlob: newContents creatingKey: &attachment->blobKey]) {
status = 500;
status = kTDStatusAttachmentError;
break;
}
}
Expand All @@ -358,11 +357,11 @@ - (NSDictionary*) attachmentsFromRevision: (TDRevision*)rev
// I just need to look it up by its "digest" property and install it into the store:
TDBlobStoreWriter *writer = [self attachmentWriterForAttachment: attachInfo];
if (!writer) {
status = 400;
status = kTDStatusBadAttachment;
break;
}
if (![writer install]) {
status = 500;
status = kTDStatusAttachmentError;
break;
}
attachment->blobKey = writer.blobKey;
Expand All @@ -378,7 +377,7 @@ - (NSDictionary*) attachmentsFromRevision: (TDRevision*)rev
if ($equal(encodingStr, @"gzip"))
attachment->encoding = kTDAttachmentEncodingGZIP;
else {
status = 400;
status = kTDStatusBadEncoding;
break;
}

Expand All @@ -404,7 +403,7 @@ - (TDStatus) processAttachments: (NSDictionary*)attachments
// If there are no attachments in the new rev, there's nothing to do:
NSDictionary* revAttachments = [rev.properties objectForKey: @"_attachments"];
if (revAttachments.count == 0 || rev.deleted)
return 200;
return kTDStatusOK;

SequenceNumber newSequence = rev.sequence;
Assert(newSequence > 0);
Expand All @@ -421,7 +420,7 @@ - (TDStatus) processAttachments: (NSDictionary*)attachments
if (attachment->revpos == 0)
attachment->revpos = generation;
else if (attachment->revpos > generation)
return 400;
return kTDStatusBadAttachment;

// Finally insert the attachment:
status = [self insertAttachment: attachment forSequence: newSequence];
Expand All @@ -432,10 +431,10 @@ - (TDStatus) processAttachments: (NSDictionary*)attachments
fromSequence: parentSequence
toSequence: newSequence];
}
if (status >= 300)
if (TDStatusIsError(status))
return status;
}
return 200;
return kTDStatusOK;
}


Expand Down Expand Up @@ -469,7 +468,7 @@ - (TDRevision*) updateAttachment: (NSString*)filename
revID: (NSString*)oldRevID
status: (TDStatus*)outStatus
{
*outStatus = 400;
*outStatus = kTDStatusBadAttachment;
if (filename.length == 0 || (body && !contentType) || (oldRevID && !docID) || (body && !docID))
return nil;

Expand All @@ -479,14 +478,14 @@ - (TDRevision*) updateAttachment: (NSString*)filename
if (oldRevID) {
// Load existing revision if this is a replacement:
*outStatus = [self loadRevisionBody: oldRev options: 0];
if (*outStatus >= 300) {
if (*outStatus == 404 && [self existsDocumentWithID: docID revisionID: nil])
*outStatus = 409; // if some other revision exists, it's a conflict
if (TDStatusIsError(*outStatus)) {
if (*outStatus == kTDStatusNotFound && [self existsDocumentWithID: docID revisionID: nil])
*outStatus = kTDStatusConflict; // if some other revision exists, it's a conflict
return nil;
}
NSDictionary* attachments = [oldRev.properties objectForKey: @"_attachments"];
if (!body && ![attachments objectForKey: filename]) {
*outStatus = 404;
*outStatus = kTDStatusAttachmentNotFound;
return nil;
}
// Remove the _attachments stubs so putRevision: doesn't copy the rows for me
Expand Down Expand Up @@ -516,7 +515,7 @@ - (TDRevision*) updateAttachment: (NSString*)filename
"FROM attachments WHERE sequence=? AND filename != ?",
$object(newRev.sequence), $object(oldRev.sequence),
filename]) {
*outStatus = 500;
*outStatus = kTDStatusDBError;
return nil;
}
}
Expand All @@ -533,22 +532,22 @@ - (TDRevision*) updateAttachment: (NSString*)filename
attachment->encodedLength = attachment->length;
attachment->length = [self decodeAttachment: body encoding: encoding].length;
if (attachment->length == 0 && attachment->encodedLength > 0) {
*outStatus = 400; // failed to decode
*outStatus = kTDStatusBadEncoding; // failed to decode
return nil;
}
}

if (![self storeBlob: body creatingKey: &attachment->blobKey]) {
*outStatus = 500;
*outStatus = kTDStatusAttachmentError;
return nil;
}

*outStatus = [self insertAttachment: attachment forSequence: newRev.sequence];
if (*outStatus >= 300)
if (TDStatusIsError(*outStatus))
return nil;
}

*outStatus = body ? 201 : 200;
*outStatus = body ? kTDStatusCreated : kTDStatusOK;
return newRev;
} @finally {
[self endTransaction: (*outStatus < 300)];
Expand All @@ -565,17 +564,17 @@ - (TDStatus) garbageCollectAttachments {
// Now collect all remaining attachment IDs and tell the store to delete all but these:
FMResultSet* r = [_fmdb executeQuery: @"SELECT DISTINCT key FROM attachments"];
if (!r)
return 500;
return kTDStatusDBError;
NSMutableSet* allKeys = [NSMutableSet set];
while ([r next]) {
[allKeys addObject: [r dataForColumnIndex: 0]];
}
[r close];
NSInteger numDeleted = [_attachments deleteBlobsExceptWithKeys: allKeys];
if (numDeleted < 0)
return 500;
return kTDStatusAttachmentError;
Log(@"Deleted %d attachments", numDeleted);
return 200;
return kTDStatusOK;
}


Expand Down
2 changes: 1 addition & 1 deletion Source/TDDatabase+Insertion.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ typedef BOOL (^TDValidationBlock) (TDRevision* newRevision,
/** Stores a new (or initial) revision of a document. This is what's invoked by a PUT or POST. As with those, the previous revision ID must be supplied when necessary and the call will fail if it doesn't match.
@param revision The revision to add. If the docID is nil, a new UUID will be assigned. Its revID must be nil. It must have a JSON body.
@param prevRevID The ID of the revision to replace (same as the "?rev=" parameter to a PUT), or nil if this is a new document.
@param allowConflict If NO, an error status 409 will be returned if the insertion would create a conflict, i.e. if the previous revision already has a child.
@param allowConflict If NO, an error status kTDStatusConflict will be returned if the insertion would create a conflict, i.e. if the previous revision already has a child.
@param status On return, an HTTP status code indicating success or failure.
@return A new TDRevision with the docID, revID and sequence filled in (but no body). */
- (TDRevision*) putRevision: (TDRevision*)revision
Expand Down
Loading

0 comments on commit 7f9ff78

Please sign in to comment.