I’m having some issue with second request(command:APPEND) of uploading video, which is made on POST /1.1/media/upload.json.
I read documentation about how to build Authentication header for video upload, and I did everything like explained here. With example data I see that Authentication header generates correctly, I’m getting exact same result. But when I’m changing consumer_key, authentication_token and other parameters to my application’s values - getting error Twitter API error : Internal error (code 131).
Here is step by step how I’m doing this after receiving positive response for command:INIT:
- Generating nonce from NSUUID, base64 encoded
NSString *nonce = [[[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0];
- getting timestamp
[NSString stringWithFormat:@"%lu", (unsigned long)[[NSDate date] timeIntervalSince1970]];
- putting all together:
NSString *baseURL = @"https://upload.twitter.com/1.1/media/upload.json";
parameters[@"oauth_consumer_key"] = @"<consumer key here>";
parameters[@"oauth_nonce"] = nonce;
parameters[@"oauth_signature_method"] = @"HMAC-SHA1";
parameters[@"oauth_timestamp"] = [NSString stringWithFormat:@"%lu", (unsigned long)[[NSDate date] timeIntervalSince1970]];
parameters[@"oauth_token"] = [Twitter sharedInstance].session.authToken;
parameters[@"oauth_version"] = @"1.0";
Based on the documentation I should not include any other than auth_* parameters when generating Authentication header.
4. Now we sorting parameters, percent encode them and generating signature base string
NSMutableCharacterSet *set = [NSMutableCharacterSet alphanumericCharacterSet];
[set addCharactersInString:@"._-"];
NSArray *sorted = [[parameters allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
NSMutableString *temp = [NSMutableString new];
for (NSString *key in sorted) {
[temp appendFormat:@"%@=%@&", key, [parameters[key] stringByAddingPercentEncodingWithAllowedCharacters:set]];
}
NSString *parameterString = [temp stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"&"]];
This produces something like followed:
oauth_consumer_key=<consumer key here>&oauth_nonce=kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1318622958&oauth_token=<oauth token here>&oauth_version=1.0
Signature base string:
NSMutableString *signatureBaseString = [NSMutableString new];
[signatureBaseString appendString:@"POST&"];
[signatureBaseString appendString:[baseURL stringByAddingPercentEncodingWithAllowedCharacters:set]];
[signatureBaseString appendString:@"&"];
[signatureBaseString appendString:[parameterString stringByAddingPercentEncodingWithAllowedCharacters:set]];
And it looks like
POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&oauth_consumer_key%3D<consumer key here>%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D<oauth token here>%26oauth_version%3D1.0
After that concatenating consumer secret + & + token secret:
NSString *consumerSecret = @"<consumer secret here>";
NSString *tokenSecret = [Twitter sharedInstance].session.authTokenSecret;
NSString *signingKey = [NSString stringWithFormat:@"%@&%@", [consumerSecret stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], [tokenSecret stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
Next step is sha1, key is signingKey and data is signatureBaseString. It will produce some 20-digit string. This will be our oauth_signature:
parameters[@"oauth_signature"] = signature;
- Lastly, generating Authentication header:
NSMutableString *DST = [NSMutableString new];
[DST appendString:@"OAuth "];
for (NSString *key in parameters.allKeys) {
[DST appendFormat:@"%@=\"%@\", ", key, [parameters[key] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
NSString *authorization = [DST stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@", "]];
- Building multipart/form-data request now:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:baseURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60];
request.HTTPMethod = @"POST";
NSString *boundary = [NSUUID UUID].UUIDString;
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
[request setValue:authorization forHTTPHeaderField:@"Authorization"];
boundary = [NSString stringWithFormat:@"--%@", boundary];
/// will acummulate HTTP body bytes into this variable
NSMutableData *HTTPBody = [NSMutableData new];
NSString *temp = [NSString stringWithFormat:@"%@\r\ncontent-disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n", boundary, @"command", @"APPEND"];
[HTTPBody appendData:[temp dataUsingEncoding:NSUTF8StringEncoding]];
temp = [NSString stringWithFormat:@"%@\r\ncontent-disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n", boundary, @"media_id", mediaID];
[HTTPBody appendData:[temp dataUsingEncoding:NSUTF8StringEncoding]];
temp = [NSString stringWithFormat:@"%@\r\ncontent-disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n", boundary, @"segment_index", @"0"];
[HTTPBody appendData:[temp dataUsingEncoding:NSUTF8StringEncoding]];
temp = [NSString stringWithFormat:@"%@\r\ncontent-disposition: form-data; name=\"%@\"\r\n\r\n", boundary, @"media"];
[HTTPBody appendData:[temp dataUsingEncoding:NSUTF8StringEncoding]];
[HTTPBody appendData:video];
[HTTPBody appendData:[[NSString stringWithFormat:@"%@--", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:HTTPBody];
Request in raw will look like following:
--RDE4NDY1OUYtQzUwMS00MzM3LUI1MjMtOTQyM0I1ODY0RUI5
Content-Disposition: form-data; name="command"
APPEND
--RDE4NDY1OUYtQzUwMS00MzM3LUI1MjMtOTQyM0I1ODY0RUI5
Content-Disposition: form-data; name="media_id"
707908369483493377
--RDE4NDY1OUYtQzUwMS00MzM3LUI1MjMtOTQyM0I1ODY0RUI5
Content-Disposition: form-data; name="segment_index"
0
--RDE4NDY1OUYtQzUwMS00MzM3LUI1MjMtOTQyM0I1ODY0RUI5
Content-Disposition: form-data; name="media"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
<VIDEO BYTES HERE>
--RDE4NDY1OUYtQzUwMS00MzM3LUI1MjMtOTQyM0I1ODY0RUI5--
Finally, executing request:
TWTRAPIClient *client = [Twitter sharedInstance].APIClient;
[client sendTwitterRequest:request completion:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
}];
After a while(depending on video size and internet connection) request fails with error
Error Domain=TwitterAPIErrorDomain Code=131 "Request failed: internal server error (500)" UserInfo={NSErrorFailingURLKey=https://upload.twitter.com/1.1/media/upload.json, NSLocalizedDescription=Request failed: internal server error (500), NSLocalizedFailureReason=Twitter API error : Internal error (code 131)}
In some other threads I saw somebody suggest to put auth_* parameters into POST body, but it doesn’t looks right.
Please help me with this