Obj-C gRPC new API receiving RST_STREAM error - objective-c

Context
Have a Obj-C app that needs to consume some server data. The communication uses gRPC.
Created a Obj-C client using `!ProtoCompiler-gRPCPlugin" and "ProtoCompiler".
Using protoc we generated the client. This client exposes two APIs for the same call: One is called "apiV1" and is deprecated, the other is "apiV2" and is suggested.
Below is an example of both:
/* This method belongs to a set of APIs that have been deprecated. Using the v2 API is recommended.
*/
- (void)someCallWithRequest:(XXXCallRequest *)request handler:(void(^)(XXXCallResponse *_Nullable response, NSError *_Nullable error))handler;
And, now, the new suggested API:
- (GRPCUnaryProtoCall *)callWithMessage:(XXXCallRequest *)message responseHandler:(id<GRPCProtoResponseHandler>)handler callOptions:(GRPCCallOptions *_Nullable)callOptions;
The problem
When we use the suggested v2 API, some of our calls receives the RST_STREAM error:
Error Domain=io.grpc Code=13 "{"created":"#1589553479.630057000","description":"Error received from peer ipv4:x.x.x.x:443","file":"/Users/me/Documents/ourapp/App/Pods/gRPC-Core/src/core/lib/surface/call.cc","file_line":1056,"grpc_message":"Received RST_STREAM with error code 2","grpc_status":13}" UserInfo={io.grpc.HeadersKey={
date = "Fri, 15 May 2020 14:37:58 GMT";
server = "nginx/1.17.8";
"strict-transport-security" = "max-age=15724800; includeSubDomains";
trailer = "Grpc-Status";
}, io.grpc.TrailersKey={
}, NSDebugDescription={"created":"#1589553479.630057000","description":"Error received from peer ipv4:x.x.x.:443","file":"/Users/me/Documents/ourapp/App/Pods/gRPC-Core/src/core/lib/surface/call.cc","file_line":1056,"grpc_message":"Received RST_STREAM with error code 2","grpc_status":13}, NSLocalizedDescription=Received RST_STREAM with error code 2}.
Here's how we are using the new gRPC API:
GRPCMutableCallOptions *callOptions = [self buildCallOptions];
XXXCallService *service = [XXXCallService serviceWithHost:host callOptions:callOptions];
XXXCallRequest *callRequestMessage = [self buildCallRequest];
[[service callWithMessage:callRequestMessage
responseHandler:[[GRPCUnaryResponseHandler alloc] initWithResponseHandler:^(XXXCallResponse* response, NSError *error) {
if (error) NSLog(#"%#.", error);
}
And, the most important: when we use the deprecated V1 API, this error never happens. But, unfortunately, it does not have support for defining the CallOptions/metadata/headers, which we require right now.

Related

Make curl call to grpc service

I'm trying to learn grpc using kotlin and make a simple grpc service with following proto definition :
syntax = "proto3";
option java_multiple_files = true;
option java_package = "br.bortoti";
option java_outer_classname = "StockProto";
option objc_class_prefix = "HLW";
package br.bortoti;
import "google/api/annotations.proto";
service StockService {
rpc GetStock (GetStockRequest) returns (Stock) {
option(google.api.http) = {
get: "V1/stocks/{stock}"
body: "*"
};
}
}
message Stock {
string ticker = 1;
}
message GetStockRequest {
string ticker = 1;
}
message GetStockReply {
string ticker = 1;
}
so, i'm basically mapping a service to a get request.
but when i try to call this url from curl like :
curl http://localhost:8088/V1/stocks/1
i get the error :
curl: (1) Received HTTP/0.9 when not allowed
and from the server side i have :
INFO: Transport failed
io.netty.handler.codec.http2.Http2Exception: Unexpected HTTP/1.x request: GET /V1/stocks/1
how can i make the server accept http 1.1 calls? is it even possible?
Maybe this is two question.
The gRPC use HTTP2 and need lots of headers so it is diffcult request by curl. Maybe you need grpcurl
And the path V1/stocks/{stock} need use grpc-gateway toghter, you can reference grpc-gateway for more detail.
Since you are learn how to use gRPC, maybe you can reference this project: helloworlde/grpc-java-sample, feel free to translate chinese.

IBM MobileFirst Sending JSON Body from iOS SDK to Java Adapter

Using IBM Mobile First PlatForm Version 7.1 I am trying to call a Java Adapter from iOS Application using following code.
[[WLResourceRequest requestWithURL:url method:WLHttpMethodPost] sendWithJSON:#{#"one":#"two"} completionHandler:^(WLResponse *response, NSError *error) {
NSLog(#"%#",response);
NSLog(#"%#",error);
}];
Java Method on adapter side looks as following.
#POST
#Consumes("application/json")
#Produces("application/json")
public String hello(JSONObject body){
return body.toString();
}
But I get Following error in response
2016-04-20 11:31:15.520 mbs-call[15092:3787968] Error Domain=com.alamofire.error.serialization.response Code=-1011 "Request failed: unsupported media type (415)" UserInfo={com.alamofire.serialization.response.error.response= { URL: http:/0.0.0.0:10080/mbs-api/adapters/basicadpt/users } { status code: 415, headers {
Connection = Close;
"Content-Language" = "en-US";
"Content-Length" = 0;
Date = "Wed, 20 Apr 2016 02:31:15 GMT";
"X-Powered-By" = "Servlet/3.0";
} }, NSErrorFailingURLKey=http://0.0.0.0:10080/mbs-api/adapters/basicadpt/users, com.alamofire.serialization.response.error.data=<>, NSLocalizedDescription=Request failed: unsupported media type (415)}
And it seems that in iOS SDK it adds header application/x-www-url-form-urlencoded in request when any method is called.
I have following 2 questions.
How to pass JSON Body to Java adapter?
Behaviour of sendWithJSON is different on iOS and Android. On Android it seems to add application/json header when we call adapter. Is that a bug or part of behaviour?
I believe this is a bug. I think that using sendWithJSON should automatically assume that the content type is application/json.
I suggest that you open a support request (PMR) so that they can improve the experience.
In the meantime, I found an easy workaround:
[request addHeaderValue:#"application/json" forName:#"Content-Type"]
Or in Swift:
request.addHeaderValue("application/json", forName: "Content-Type")
I had this same issue with the cordova version of an application.
var userIDTag = 'some_string';
var subTag = [userIDTag]; //<- this worked
var subTag = userIDTag; //<- this failed with the above error
var subTag = '[\'' + some_string + '\']'; //<- this also failed with the above error
The below is what I ended up doing for a Cordova app.
function subscribeByTag(userIDTag) {
var subTag = [userIDTag];
console.log("subTag: " + subTag);
WLAuthorizationManager.obtainAccessToken("push.mobileclient").then(
MFPPush.subscribe(
subTag,
function(subTag) {
navigator.notification.alert("Subscribed successfully");
},
function(failureResponse){
navigator.notification.alert("Failed to subscribe");
console.log("Failedtosubscribe:" + JSON.stringify(failureResponse));
}
)
);
}

Native iOS application Development using Worklight API and JSON parser with HTTP Adapter

WLProcedureInvocationData *myInvocationData = [[WLProcedureInvocationData alloc] initWithAdapterName:#"myRESTAdapter" procedureName:#"getAgents"];
MyInvokeListener *invokeListener = [[MyInvokeListener alloc] initWithController: self];
[[WLClient sharedInstance] invokeProcedure:myInvocationData withDelegate:invokeListener];
[__NSDictionaryI WLJSONRepresentation]: unrecognized selector sent to
instance
I am trying to fetch the response using JSON service with HTTP Adapter in Worklight. When i deploy the API and proceeds further then am getting the above exception..
Note: The service am using only to fetch details with out any parameter parsing...
Adapter code:
function getAgents() {
var input = {
method : 'get',
returnedContentType : 'json',
path : 'myApp/agents'
};
return WL.Server.invokeHttp(input);
}
Can anyone help on this?
Thanks in advance

NSURLConnection returning error instead of response for 401

I have a web API that, for a specific request returns status code 200 if everything went ok, and 401 if the user is not logged in based on an Authorization token. Everything works fine if the response status is 200, but doesn't seem to work properly if the response status is 401, returning a connection error with code -1012, while the response is nil.
So, the following code:
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(#"%#", response);
NSLog(#"%#", connectionError);
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
int statusCode = (int)[httpResponse statusCode];
NSLog(#"response status code: %d", statusCode);
will display
2015-04-01 15:58:18.511 MyProject[3618:694604] <NSHTTPURLResponse: 0x155facc0> { URL: *SOME_URL* } { status code: 200, headers {
"Access-Control-Allow-Headers" = "Content-Type, Accept, X-Requested-With";
"Access-Control-Allow-Methods" = "POST, GET, PUT, UPDATE, OPTIONS";
"Access-Control-Allow-Origin" = "*";
Connection = "keep-alive";
"Content-Type" = "application/json";
Date = "Wed, 01 Apr 2015 12:58:14 GMT";
Server = "Wildfly 8";
"Transfer-Encoding" = Identity;
"X-Powered-By" = "Undertow 1";
} }
2015-04-01 15:58:18.513 MyProject[3618:694604] (null)
2015-04-01 15:58:18.513 MyProject[3618:694604] response status code: 200
if the response status is 200, while if the status code is 401, I will get:
2015-04-01 16:05:55.988 MyProject[3633:695836] (null)
2015-04-01 16:05:55.992 MyProject[3633:695836] Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x146137c0 {NSErrorFailingURLKey=*SOME_URL*, NSErrorFailingURLStringKey=*SOME_URL*, NSUnderlyingError=0x1459e6d0 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error -1012.)"}
2015-04-01 16:05:55.992 MyProject[3633:695836] response status code: 0
If I do the same request using Postman or an Android device, I will get status code 401 with the following headers(copied from Postman):
Connection → keep-alive
Content-Length → 30
Content-Type → application/json
Date → Wed, 01 Apr 2015 13:07:34 GMT
Server → Wildfly 8
X-Powered-By → Undertow 1
Is there any fix or maybe a library that could give me some accurate response status? I searched a bit about the -1012 error, but couldn't find much and I don't really want to base on that.
Edit: after a bit of research I found the following statement on Appl's documentation: "If authentication is required in order to download the request, the required credentials must be specified as part of the URL. If authentication fails, or credentials are missing, the connection will attempt to continue without credentials."
But then how can I know if this error will be after a 401 status? Can it appear after another type of request?
to check the 401 error, you can do this:
if (error != nil && error.code == NSURLErrorUserCancelledAuthentication) {
// do something for 401 error
}
hope this help
In order to get the 401 status code, I think you'll need to implement protocol NSURLConnectionDelegate and then connection:didReceiveAuthenticationChallenge:.
So, you'll also need to pass the delegate, maybe using [NSURLConnection connectionWithRequest:request delegate:self].
And, if you aren't trying to implement the authentication challenge, I would rather always return the 200 status code, but with different json content.
Hope it can help.
I have a web API that, for a specific request returns status code 200
if everything went ok, and 401 if the user is not logged in based on
an Authorization token. Everything works fine if the response status
is 200, but doesn't seem to work properly if the response status is
401, returning a connection error with code -1012, while the response
is nil.
I've run exactly into the same problem, REST API call returns 401 in Android and Postman, but status code 0 in iOS with a connection error with code -1012.
You can find more information about this problem in this SO post.
Seems to be an iOS bug (or at least very strange approach) happening both with async and sync requests.
I'm posting this just in case this might be useful for others bumping into the same issue. What I did in the code to manage the 401 status, right after the request, is to call a method that checks each managed http status code.
The method receives (NSError **)error and [(NSHTTPURLResponse *)response statusCode].
This is the 401 part - uses the error details stored inside userInfo structure.
// Auth failed or token problems
if (statusCode == 0 && [error userInfo] != nil) {
// Get error detailed informations
NSDictionary *userInfo = [error userInfo];
NSString *errorString = [[userInfo objectForKey:NSUnderlyingErrorKey] localizedDescription];
// If error message is of type authFailed or returns iOS code -1012 meaning auth failed
if ([errorString containsString:#"kCFErrorDomainCFNetwork"] || [errorString containsString:#"-1012"]) {
NSLog(#"%# - ConnectionError - Code 401 - Cause: authentication failed, token is invalid or expired", type);
} else {
NSLog(#"%# - ConnectionError - Cause: generic auth error", type);
}
// Alert user that auth failed
...
}
The other way is to check directly for this error code (-1012) as suggested by #ThuanDINH above. (edited the answer for objective c).
My code can be changed into:
// Auth failed - token problems
if (statusCode == 0 && [error code] == NSURLErrorUserCancelledAuthentication) {
// If error code is UserCanceledAuthentication
MPLog(MPLogLevelInfo, #"%# - ConnectionError - Cause: authentication failed, token is invalid or expired", type);
// Alert user that auth failed
...
}
But in this way you will not handle all other errors (you need to switch on each NSURLError code).
Here you can find the SO question with the list of all the NSURLError codes.
Based on Apple documentation https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/NSURLSessionConcepts/NSURLSessionConcepts.html,
Note: NSURLSession does not report server errors through the error parameter. The only errors your delegate receives through the error parameter are client-side errors, such as being unable to resolve the hostname or connect to the host. The error codes are described in URL Loading System Error Codes.
Server-side errors are reported through the HTTP status code in the NSHTTPURLResponse object. For more information, read the documentation for the NSHTTPURLResponse and NSURLResponse classes.
We need to make sure that we do not cancel the session in our code. For example, I was calling NSURLSessionAuthChallengeCancelAuthenticationChallenge in didReceiveChallenge delegate method, when previousFailureCount > 1. This was suppressing 401 response and also call to didReceiveResponse.
When I changed above value to NSURLSessionAuthChallengePerformDefaultHandling, I am receiving 401 Unauthorized response in didReceiveResponse delegate method and works as expected.

How to use BreezeJS with a WCF Data Service cross origin?

I'm using BreezeJS to access a WCF Odata service in my application. It works great when accessing the service on my local machine, but when I try to run the app against the service on my dev web server, it fails.
The first thing I did was to add headers to the service response to allow cross-origin requests:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS, MERGE, DELETE, PUT
Access-Control-Allow-Headers: Content-Type, Accept, DataServiceVersion, MaxDataServiceVersion
Also I set the datajs requests to use JSONP as such:
var oldClient = OData.defaultHttpClient;
var myClient = {
request: function (request, success, error) {
request.enableJsonpCallback = true;
return oldClient.request(request, success, error);
},
};
OData.defaultHttpClient = myClient;
My breeze setup is:
breeze.config.initializeAdapterInstance('dataService', 'odata', true);
this.dataService = new breeze.DataService({
serviceName: 'http://mydevserver/AirVision.Web.Site/AvDataService.svc/',
hasServerMetadata: true,
useJsonp: true,
});
this.manager = new breeze.EntityManager({ dataService: this.dataService });
That mostly works. However, it blows up if I try to use an expand():
this.loadSites = function() {
var query = new breeze.EntityQuery().from("SourceSite")
.expand("SourceParameter")
;
return this.manager.executeQuery(query);
}
I get an error from breeze:
"TypeError: undefined is not a function
at setNpValue (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:5189:41)
at defaultPropertyInterceptor [as _$interceptor] (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:4913:13)
at descr.set [as System] (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:16161:22)
at protoFn.initializeEntityPrototype.proto.setProperty (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:16034:32)
at updateRelatedEntity (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:14774:26)
at mergeRelatedEntity (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:14715:13)
at http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:14698:17
at Array.forEach (native)
at updateEntity (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:14696:41)
at mergeEntity (http://localhost/AirVision.Web.Site/Scripts/breeze.debug.js:14667:13)"
I've stepped through the breeze.debug.js and it comes down to this line:
var siblings = newValue.getProperty(inverseProp.name);
On that line, the newValue object (which represents one of my data entities) does NOT have the getProperty function defined.
Is there something I need to do, either on the server side or in the javascript client, to fix this?
Thanks!
It could be that the metadata does not properly define your relationship from 'SourceSite' to 'SourceParameter'. I've had this error before when there was a problem with the metadata and breeze couldn't find the entity/relationship to serialize the data to.
I would start by ensuring you the relationships, and the entity 'SourceParameter' for that matter, are properly defined in your server side breeze metadata.