Permalink
Browse files

Better handling of bad data from upstream server

* Changed kTDStatusUpstreamError from 502 to 589 to avoid confusion
* Report detailed info about what went wrong parsing _changes feed
* Report detailed info about what went wrong parsing multipart response
  • Loading branch information...
1 parent e7729f8 commit 9f0a96bb840658f17a9b81d2f0494c443bfdb736 @snej snej committed Sep 24, 2012
@@ -85,7 +85,7 @@ typedef enum TDChangeTrackerMode {
@property (readonly) NSString* changesFeedPath;
- (void) setUpstreamError: (NSString*)message;
- (void) failedWithError: (NSError*)error;
-- (NSInteger) receivedPollResponse: (NSData*)body;
+- (NSInteger) receivedPollResponse: (NSData*)body errorMessage: (NSString**)errorMessage;
- (void) stopped; // override this
@end
@@ -185,23 +185,37 @@ - (BOOL) receivedChange: (NSDictionary*)change {
return YES;
}
-- (NSInteger) receivedPollResponse: (NSData*)body {
- if (!body)
+- (NSInteger) receivedPollResponse: (NSData*)body errorMessage: (NSString**)errorMessage {
+ if (!body) {
+ *errorMessage = @"No body in response";
return -1;
- id changeObj = [TDJSON JSONObjectWithData: body options: 0 error: NULL];
+ }
+ NSError* error;
+ id changeObj = [TDJSON JSONObjectWithData: body options: 0 error: &error];
+ if (!changeObj) {
+ *errorMessage = error.localizedDescription;
+ return -1;
+ }
NSDictionary* changeDict = $castIf(NSDictionary, changeObj);
NSArray* changes = $castIf(NSArray, changeDict[@"results"]);
- if (!changes)
+ if (!changes) {
+ *errorMessage = @"No 'changes' array in response";
return -1;
+ }
if ([_client respondsToSelector: @selector(changeTrackerReceivedChanges:)]) {
[_client changeTrackerReceivedChanges: changes];
if (changes.count > 0)
self.lastSequenceID = [[changes lastObject] objectForKey: @"seq"];
} else {
for (NSDictionary* change in changes) {
- if (![self receivedChange: change])
+ if (![self receivedChange: change]) {
+ *errorMessage = $sprintf(@"Invalid change object: %@",
+ [TDJSON stringWithJSONObject: change
+ options:TDJSONWritingAllowFragments
+ error: nil]);
return -1;
+ }
}
}
return changes.count;
@@ -225,7 +225,8 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSData* input = [_inputBuffer retain];
LogTo(ChangeTracker, @"%@: Got entire body, %u bytes", self, (unsigned)input.length);
BOOL restart = NO;
- NSInteger numChanges = [self receivedPollResponse: input];
+ NSString* errorMessage = nil;
+ NSInteger numChanges = [self receivedPollResponse: input errorMessage: &errorMessage];
if (numChanges < 0) {
// Oops, unparseable response:
if (_mode == kLongPoll && [input isEqualToData: [@"{\"results\":[\n"
@@ -240,7 +241,7 @@ - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
}
}
if (!restart)
- [self setUpstreamError: @"Unparseable server response"];
+ [self setUpstreamError: errorMessage];
} else {
// Poll again if there was no error, and either we're in longpoll mode or it looks like we
// ran out of changes due to a _limit rather than because we hit the end.
@@ -105,8 +105,9 @@ - (BOOL) setContentType: (NSString*)contentType {
- (BOOL) appendData:(NSData *)data {
if (_multipartReader) {
[_multipartReader appendData: data];
- if (_multipartReader.failed) {
- Warn(@"%@: received unparseable MIME multipart response", self);
+ if (_multipartReader.error) {
+ Warn(@"%@: received unparseable MIME multipart response: %@",
+ self, _multipartReader.error);
_status = kTDStatusUpstreamError;
return NO;
}
@@ -19,6 +19,7 @@
NSMutableData* _buffer;
NSMutableDictionary* _headers;
int _state;
+ NSString* _error;
}
/** Initializes the reader.
@@ -35,7 +36,7 @@
@property (readonly) BOOL finished;
/** Was there a fatal parse error? */
-@property (readonly) BOOL failed;
+@property (readonly) NSString* error;
/** The MIME headers of the part currently being parsed.
You can call this from your -appendToPart and/or -finishedPart overrides. */
View
@@ -83,6 +83,7 @@ - (void) close {
- (void)dealloc {
[self close];
+ [_error release];
[super dealloc];
}
@@ -122,8 +123,10 @@ - (BOOL) parseContentType: (NSString*)contentType {
- (BOOL) parseHeaders: (NSString*)headersStr {
- if (!headersStr)
+ if (!headersStr) {
+ self.error = @"Unparseable UTF-8 in headers";
return NO;
+ }
[_headers release];
_headers = [[NSMutableDictionary alloc] init];
BOOL first = YES;
@@ -132,8 +135,10 @@ - (BOOL) parseHeaders: (NSString*)headersStr {
first = NO; // first line is just the whitespace between separator and its CRLF
else {
NSRange colon = [header rangeOfString: @":"];
- if (colon.length == 0)
+ if (colon.length == 0) {
+ self.error = @"Missing ':' in header line";
return NO;
+ }
NSString* key = trim([header substringToIndex: colon.location]);
NSString* value = trim([header substringFromIndex: NSMaxRange(colon)]);
_headers[key] = value;
@@ -165,8 +170,14 @@ - (void) trimBuffer {
}
-- (void) setFailed {
+- (NSString*) error {
+ return _error;
+}
+
+- (void) setError: (NSString*)error {
_state = kFailed;
+ if (!_error)
+ _error = [error copy];
[self close];
}
@@ -235,10 +246,8 @@ - (void) appendData: (NSData*)data {
freeWhenDone: NO];
BOOL ok = [self parseHeaders: headers];
[headers release];
- if (!ok) {
- [self setFailed];
- return;
- }
+ if (!ok)
+ return; // parseHeaders already set .error
[self deleteUpThrough: r];
[_delegate startedPart: _headers];
nextState = kInBody;
@@ -247,7 +256,7 @@ - (void) appendData: (NSData*)data {
}
default:
- [self setFailed];
+ self.error = @"Unexpected data after end of MIME body";
return;
}
if (nextState > 0)
@@ -260,10 +269,6 @@ - (BOOL) finished {
return _state == kAtEnd;
}
-- (BOOL) failed {
- return _state == kFailed;
-}
-
#if DEBUG
- (NSData*) boundary {return _boundary;}
@@ -362,7 +367,7 @@ - (void)dealloc {
CAssert(r.location < mime.length, @"Parser didn't stop at end");
r.length = MIN(chunkSize, mime.length - r.location);
[reader appendData: [mime subdataWithRange: r]];
- CAssert(!reader.failed, @"Reader got a parse error");
+ CAssert(!reader.error, @"Reader got a parse error: %@", reader.error);
r.location += chunkSize;
} while (!reader.finished);
CAssertEqual(delegate.partList, expectedParts);
View
@@ -24,7 +24,6 @@ typedef enum {
kTDStatusUnsupportedType= 415,
kTDStatusServerError = 500,
- kTDStatusUpstreamError = 502, // aka Bad Gateway -- upstream server error
// Non-HTTP errors:
kTDStatusBadEncoding = 490,
@@ -35,6 +34,7 @@ typedef enum {
kTDStatusBadParam = 495,
kTDStatusDeleted = 496, // Document deleted
+ kTDStatusUpstreamError = 589, // Error from remote replication server
kTDStatusDBError = 590, // SQLite error
kTDStatusCorruptError = 591, // bad data in database
kTDStatusAttachmentError= 592, // problem with attachment store
View
@@ -27,8 +27,7 @@
static const struct StatusMapEntry kStatusMap[] = {
{kTDStatusNotFound, 404, @"not_found"}, // for compatibility with CouchDB
- {kTDStatusDuplicate, 412, @"Already exists"}, // really 'Precondition Failed'
- {kTDStatusUpstreamError, 502, @"Remote server error"}, // really 'Bad Gateway'
+ {kTDStatusDuplicate, 412, @"Already exists"}, // really 'Precondition Failed'
{kTDStatusBadEncoding, 400, @"Bad data encoding"},
{kTDStatusBadAttachment, 400, @"Invalid attachment"},
@@ -38,6 +37,7 @@
{kTDStatusBadParam, 400, @"Invalid parameter in JSON body"},
{kTDStatusDeleted, 404, @"deleted"},
+ {kTDStatusUpstreamError, 502, @"Invalid response from remote replication server"},
{kTDStatusDBError, 500, @"Database error!"},
{kTDStatusCorruptError, 500, @"Invalid data in database"},
{kTDStatusAttachmentError, 500, @"Attachment store error"},

0 comments on commit 9f0a96b

Please sign in to comment.