AFNetworking 2: What's it actually sending? - afnetworking-2

I'm using POST:parameters:constructingBodyWithBlock:. I believe the pieces I'm attempting to send to be correct, but I'm getting a HTTP 400 from the server. So something is wrong with the overall packet or how a piece of it is being encoded. I have a few theories about that, but before I do anything else I'd like to see what the data I'm sending actually looks like.
What is the best way to see what AFNetworking is actually sending for debugging purposes?
I've tried tracing quite deep, but when I reach AFMultipartBodyStream I get lost. I'm not sure how to capture its data without severely hacking at AFNetworking.

For anyone curious later, I've found this works nicely in the failure or success block:
NSMutableData *data = [NSMutableData data];
NSInputStream *stream = [operation.request.HTTPBodyStream copy];
[stream open];
BOOL done = NO;
while (!done) {
NSMutableData *buffer = [NSMutableData dataWithLength:1024];
buffer.length = [stream read:buffer.mutableBytes maxLength:buffer.length];
if (buffer.length) {
[data appendData:buffer];
} else {
done = YES;
}
}
[stream close];
The resulting data object can be written to disk, but can only be converted to a string directly if the parameters were entirely string (and not a binary file).

Related

InDesign Socket HTTP response is missing sections

I am automating Adobe InDesign to create documents using JSON data gathered from a web API with a SQL Server backend. I am using the Sockets object to make an HTTP 1.0 call to our server. Sometimes the response received is missing about 1700 characters from various points within the JSON string, yet when I call the same API endpoint using curl or Postman I get a complete and valid response.
The response should be about 150k characters long, and I'm using conn.read(99999999) to read it. In addition, the appearance of the end of the string looks correct, so I don't believe it's any kind of truncation problem.
The problem only seems to occur when I request a UTF-8 encoding. If I request ASCII I get a complete and valid response, but missing various Unicode characters. If I request BINARY I get a complete and valid response but the JavaScript/ExtendScript seems to be handling any multi-byte Unicode characters received as individual bytes, rather than as the Unicode characters we want to display.
Here is an illustration of the behavior I'm seeing, using bogus data...
"Expected" response...
[{"Id":1, "name":"Random Name", "Text":"A bunch of text", "AnotherId": 1}]
"Actual" response...
[{"Id":1, "name":"Random Name", "Text":"A bunc": 1}]
The problem first manifested itself as a JSON2 parsing error, for obvious reasons, but the root of it seems to be the fact that parts of the data are going missing in-transit.
So far we've only seen this problem when making the call using the InDesign Sockets object, and not every response exhibits this behavior.
Any help or insights you could offer would be appreciated.
Here is the function I'm using to call for data...
function httpRequest(url, encoding) {
try {
var response = "";
var hostName = getHostFromUrl(url);
var pathAndQuery = getPathAndQueryFromUrl(url);
var httpGet = "GET ";
httpGet += pathAndQuery;
httpGet += " HTTP/1.0\r\nHost: ";
httpGet += hostName;
httpGet += "\r\n";
var conn = new Socket;
conn.timeout = 30;
//conn.encoding = encoding || "UTF-8";
//conn.charset = "UTF-16";
if (conn.open(hostName + ":80", encoding || "UTF-8")) {
// send a HTTP GET request
conn.writeln(httpGet);
// and read the server's response
response = conn.read(99999999);
conn.close();
}
return parseHttpResponse(response);
}
catch (e) {
$.writeln(e);
$.global.alert("There was a problem making an HTTP Request: " + e);
return null;
}
}
It turns out my handling of the HTTP response was too simplistic and needed extra logic to handle Unicode characters properly.
The solution, in my case, was to use the GetURL method made available by Kris Coppieter here.

Optimize performance :Passing large JSON from Servlet to JSP

I have to pass a json array consisting of json objects from the servlet to the jsp page. This data transfer slows down page responsiveness. Is there any way to optimize performance while passing large json from servlet to jsp.
code looks like:
request.setAttribute("jsonStringForDataTable", jsonArrayForDataTable);
response.setContentType("text/plain");
response.setContentLength(jsonArrayForDataTable.toString().getBytes().length);
ServletOutputStream out=response.getOutputStream();
out.print(jsonArrayForDataTable.toString().replace('_',' '));
out.close();
...
Any help is highly appreciated
You're using ajax, right? It's hard to tell since you're setting that request attribute, as if about to forward to a jsp, but then you're writing directly to the response. If this is ajax, you can compress the response with gzip if the client accepts it. You'll have to try it to know if it will speed things up in your situation. You'll be reducing the size of the response by a ratio that depends on the original content, but you'll be increasing the amount of processor work on the server (and on the client, which has to decompress the response).
ServletOutputStream out = response.getOutputStream();
response.setContentType("application/json");
String strVal = jsonArrayForDataTable.toString().replace('_',''));
if (request.getHeader("Accept-Encoding") != null && request.getHeader("Accept-Encoding").contains("gzip"))
{
ByteArrayOutputStream baos = new ByteArrayOutputStream(strVal.length());
GZipOutputStream gzip = new GZIPOutputStream(baos);
gzip.write(strVal.getBytes());
gzip.close();
response.setHeader("Content-Encoding", "gzip");
out.write(baos.toByteArray());
baos.close();
}
else
{
out.print(strVal);
}
On the client side, you have to be prepared for a gzipped response by setting the Accept-Encoding XMLHttpRequest header to gzip. The browser will decompress it into the json string.

Correct way to use AFHTTPSessionManager as a singleton?

I'm trying to use AFNetworking 2.0 to perform my network requests but I'm running into some odd behavior. I've subclassed AFHTTPSessionManager as suggested in the documentation and provided a class method that returns a singleton object that has the base url set as well as sets my auth header.
+ (id)sharedInstance {
static dispatch_once_t once;
static MyHTTPClient *sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] initWithBaseURL: NSURLURLWithString:kPlatformAPIBaseURL]];
});
//Uncommenting this line makes the error go away
//sharedInstance.responseSerializer = [AFJSONResponseSerializer serializer];
//get latest session id everytime someone gets an instance of the client
sharedInstance.sessionId = [MySessionManager getSessionId];
return sharedInstance;
}
- (instancetype)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
if(self) {
self.sessionId = [FSSessionManager getSessionId];
self.serializer = [AFHTTPRequestSerializer serializer];
[_serializer setValue:_sessionId forHTTPHeaderField:kAuthorizationHeader];
[_serializer setValue:#"application/json" forHTTPHeaderField:kAcceptHeader];
self.requestSerializer = _serializer;
}
return self;
}
- (void)setSessionId:(NSString *)sessionId {
_sessionId = sessionId;
[self.serializer setValue:_sessionId forHTTPHeaderField:kAuthorizationHeader];
}
My app uses this to make a POST request to authenticate my user. That works great. I then make a GET request to retrieve a list of objects. Also works great. I then make the same GET request and I get back a network error Error Domain=AFNetworkingErrorDomain Code=-1016 "Request failed: unacceptable content-type: application/json" It's the exact same GET request but it fails on the second call. When I uncomment the sharedInstance.responseSerializer line so I create a new instance of the response serializer each time I get a reference to my shared instance then I don't get this error anymore.
Can a responseSerializer not be used multiple times safely? It feels like some sort of state is hanging around across requests. What's the correct way to set this up?
A response serializer can be used multiple times safely. Based on the error message you posted, "unacceptable content-type: application/json ", it appears you're setting responseSerializer to something else elsewhere in your code. JSON will serialize properly as long as it's set to [AFJSONResponseSerializer serializer].

AFNetworking + JSON + progress download

I'm using AFNetworking and like it very much.
I need to get JSON data from my server and it's OK, it works perfectly.
I added the setDownloadProgressBlock but I think it can't work with JSON download: maybe it's not possible to get the estimated amount of bytes to download.
My code:
NSMutableURLRequest *request = [[VinocelaHTTPClient sharedClient] requestWithMethod:#"GET" path:#"ws/webapp/services/pull" parameters:nil];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
}];
[operation setDownloadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
NSLog(#"Get %d of %d bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
[operation start];
And my result :
Get 27129 of -1 bytes
Get 127481 of -1 bytes
Get 176699 of -1 bytes
So, I think AFNetworking can't estimate the real size to download when downloading JSON data contrary to a zip file or an image ?
From perusing the source, it seems that the progress callback is just passed the expectedContentLength property of the cached, internal NSHTTPURLResponse object. So, if for some reason your server isn't correctly sending the Content-Length header, and/or is doing chunked transfer encoding, that value is unknown, and the value NSURLResponseUnknownLength is returned (which happens to be defined as -1).
Try inspecting the headers returned by an HTTP request outside the context of your app. If you get a Content-Length header with a sane value, the problem likely lies in AFNetworking itself. If it is absent, the problem lies with the server. I've never seen an HTTP server send a JSON response using chunked transfer encoding (most of the time the content size should be relatively small and known at the time the headers are sent), but it's within spec for it to do so.

HTTP Compression of JSON Data on Windows Azure Web Role

I've got compression propperly configured for my Azure web role. Both .aspx pages and static pages like *.css are being compressed correctly.
<urlCompression doStaticCompression="true" doDynamicCompression="true" dynamicCompressionBeforeCache="true" />
I've got several different [System.Web.Services.WebMethod]'s though, that are not returning GZIP'd data. The size of each request is around 350KB, so I'm thinking it should be quite a bit faster if I can get this to work.
Within my webMethod, I create a list of objects, return the objects, and I assume some type of built in serializer turns this into JSON?
Is there anyway to force this content to be compressed?
Thanks so much!
I've seen people have issues with built in Compression for numerous reasons
The simplest way is to use a third party component such as Telerik's RadCompression to enforce compression on the response to AJAX calls.
Alternatively, you can override the application's BeginRequest method or write your own handler
to pack up the responses on the fly. A basic VB version of how to do this is here:
Sub Application_BeginRequest(...)
If Request.RawUrl.Contains(".aspx") And _
Not Request.Headers("Accept-Encoding") Is Nothing Then
If Request.Headers("Accept-
encoding").ToLower().Contains("gzip") Then
Response.Filter = New GZipStream(Response.Filter,CompressionMode.Compress, True)
Response.AppendHeader("Content-encoding", "gzip")
' Else...attempt deflate if GZip is not allowed
End If
End If
End Sub
I've done a method with the handler as well (and that's what I believe Telerik's RadCompression uses), but it is a good bit more complicated as you have to modify the response size, etc.
Here's what I ended up with, a variation Yak's answer.
HttpApplication app = (HttpApplication)sender;
HttpRequest request = app.Request;
HttpResponse response = app.Response;
System.Web.HttpApplication Appl = (System.Web.HttpApplication)sender;
HttpContext context = Appl.Context;
string origpath = context.Request.Url.AbsolutePath;
//Ajax Web Service request is always starts with application/json
if (request.ContentType.ToLower(CultureInfo.InvariantCulture).StartsWith("application/json"))
{
//User may be using an older version of IE which does not support compression, so skip those
if (!((request.Browser.IsBrowser("IE")) && (request.Browser.MajorVersion <= 6)))
{
string acceptEncoding = request.Headers["Accept-Encoding"];
if (!string.IsNullOrEmpty(acceptEncoding))
{
acceptEncoding = acceptEncoding.ToLower(CultureInfo.InvariantCulture);
if (acceptEncoding.Contains("gzip"))
{
response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
response.AddHeader("Content-encoding", "gzip");
}
else if (acceptEncoding.Contains("deflate"))
{
response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
response.AddHeader("Content-encoding", "deflate");
}
}
}
}