similar to this but this time i need to retrieve the JSOn response of the server.
here is my existing code:
return Observable.create{ observer in
let _ = self.provider
.request(.getMerchantDetails(qrId: qrId))
.filterSuccessfulStatusCodes()
.mapJSON()
.subscribe(onNext: { response in
observer.onNext(RQRMerchant(json: JSON(response)))
}, onError: { error in
observer.onError(error)
})
return Disposables.create()
my question is: I can get the error response code 404 by error.localizedDescription But I also want to get the JSON response of the 404 HTTP request.
I've been faced with the same problem, and for me the easiest and cleanest solution was to extend MoyaError to include a property for the decoded error object. In my case I'm using Decodable objects, so you could write something like this for a decodable BackendError representing the error you may get from your server:
extension MoyaError {
public var backendError: BackendError? {
return response.flatMap {
try? $0.map(BackendError.self)
}
}
}
If you instead prefer to directly deal with JSON you can invoke the mapJSONmethod instead of mapping to a Decodable.
Then you just have to do the following to get the error information for non successful status codes:
onError: { error in
let backendError = (error as? MoyaError).backendError
}
Since the response of your server is also contained in a JSON, that means that your onNext emissions can be successful JSON responses or invalid JSON responses.
Check the validity of the response using do operator
You can check for the validity of the response by doing the following:
return Observable.create{ observer in
let _ = self.provider
.request(.getMerchantDetails(qrId: qrId))
.filterSuccessfulStatusCodes()
.mapJSON()
.do(onNext: { response in
let isValidResponse : Bool = false // check if response is valid
if !isValidResponse {
throw CustomError.reason
}
})
.subscribe(onNext: { response in
observer.onNext(RQRMerchant(json: JSON(response)))
}, onError: { error in
observer.onError(error)
})
return Disposables.create()
Use the do operator
Check if the onNext emission is indeed a valid emission
Throw an error if it is invalid, signifying that the observable operation has failed.
Response validation
To keep your response validation code in the right place, you can define a class function within your response class definition that verifies if it is valid or not:
class ResponseOfTypeA {
public class func isValid(response: ResponseOfTypeA) throws {
if errorConditionIsTrue {
throw CustomError.reason
}
}
}
So that you can do the following:
// Your observable sequence
.mapJSON()
.do(onNext: ResponseOfTypeA.isValid)
.subscribe(onNext: { response in
// the rest of your code
})
Related
I would like to add a decorator to my armeria client that checks each http response if a certain http header was returned:
builder.decorator((delegate, ctx, req) -> {
final HttpResponse response = delegate.execute(ctx, req);
final AggregatedHttpResponse r = response.aggregate().join();
for (Map.Entry<AsciiString, String> header : r.headers()) {
if ("warning".equalsIgnoreCase(header.getKey().toString())) {
throw new IllegalArgumentException("Detected usage of deprecated API for request "
+ req.toString() + ":\n" + header.getValue());
}
}
return response;
});
However, when starting my client it blocks on the join() call and waits forever. Is there a standard pattern for this in Armeria ? Presumably i cannot just block on the response in an interceptor, but i could not find a way to access the response headers otherwise. Using subscribe or toDuplicator did not work any better though.
There are two ways to achieve the desired behavior.
The first option is to aggregate the response asynchronously and then convert it back to an HttpResponse. The key APIs are AggregatedHttpResponse.toHttpResponse() and HttpResponse.from(CompletionStage):
builder.decorator(delegate, ctx, req) -> {
final HttpResponse res = delegate.serve(ctx, req);
return HttpResponse.from(res.aggregate().thenApply(r -> {
final ResponseHeaders headers = r.headers();
if (headers...) {
throw new IllegalArgumentException();
}
// Convert AggregatedHttpResponse back to HttpResponse.
return r.toHttpResponse();
}));
});
This approach is fairly simple and straightforward, but it doesn't work for a streaming response, because it waits until the complete response body is ready.
If your service returns a potentially large streaming response, you can use a FilteredHttpResponse to filter the response without aggregating anything:
builder.decorator(delegate, ctx, req) -> {
final HttpResponse res = delegate.serve(ctx, req);
return new FilteredHttpResponse(res) {
#Override
public HttpObject filter(HttpObject obj) {
// Ignore other objects like HttpData.
if (!(obj instanceof ResponseHeaders)) {
return obj;
}
final ResponseHeaders headers = (ResponseHeaders) obj;
if (headers...) {
throw new IllegalArgumentException();
}
return obj;
}
};
});
It's slightly more verbose than the first option, but it does not buffer the response in the memory, which is great for large streaming responses.
Ideally, in the future, we'd like to add more operators to HttpResponse or StreamMessage. Please stay tuned to this issue page and add any suggestions for better API: https://github.com/line/armeria/issues/3097
I would like to return not only JSON, but also the HTTP response code.
I registering REST interface through URLRouter:
router.registerRestInterface(new ClientServerAPI);
Example of my REST implementation:
module clienserverapi.clientserver;
import api.clientserver;
import models.replies.client_versions;
/**
Implementation of Client-Server API.
*/
class ClientServerAPI : IClientServerAPI {
#safe:
ClientVersions getSupportedClientVersions() {
bool[string] unstableFeatures;
return ClientVersions(supportedVersions.dup, unstableFeatures);
}
}
In the REST interface generator the response codes are handled automatically and as you can't pass in a HTTPServerResponse/HTTPServerRequest argument into your REST methods, you can't control what status gets returned.
However there are some built-in statuses which get handled:
200/204 are returned based on content
400 Bad request for mismatched arguments
404 Not found for unmatched routes
500 internal server error is returned on most exceptions
(outside of debug mode) unauthorized / bad request / forbidden are sent
See also: REST interface documentation
and you can control any status code using HTTPStatusException, however it is treated as error and will result in a predefined error json which has statusMessage as exception message set and returns the HTTP status code you pass to it. (this is probably what you want for error handling)
You can also change what the errors look like by setting the errorHandler to a RestErrorHandler delegate in your RestInterfaceSettings.
Alternatively, depending on what you want to do, you can use a WebInterface which is a lot like a rest interface, but doesn't have some convenience functions REST interfaces do, but instead can fully access the request/response arguments and can basically do anything like a normal http route and has some other convenience functions you can use.
In theory you could abuse the errorHandler + HTTPStatusException with valid HTTP status codes if you want to return custom success codes with your data, but I would discourage that and instead go with web interfaces if that's what you are after.
However if all you want to do is having custom error codes with a custom, but consistent, error page then I would definitely go with REST interface with an errorHandler.
Your could could now look like this:
import vibe.vibe;
import std.uni;
#safe:
void main() {
auto server = new HTTPServerSettings;
server.port = 3000;
server.bindAddresses = ["::1", "127.0.0.1"];
auto router = new URLRouter;
RestInterfaceSettings settings = new RestInterfaceSettings();
// this is how the error page will look on any thrown exception (like HTTPStatusException)
settings.errorHandler = (HTTPServerRequest req, HTTPServerResponse res,
RestErrorInformation error) #safe {
res.writeJsonBody([
// design this however you like
"ok": Json(false),
"error": serializeToJson([
"status": Json(cast(int)error.statusCode),
"message": Json(error.exception.msg),
"parent": Json("/api/something")
])
]);
};
router.registerRestInterface(new Impl, settings);
listenHTTP(server, router);
runApplication();
}
interface RestAPI {
string getGreeting(string name);
}
class Impl : RestAPI {
string getGreeting(string name)
{
// throw an HTTP Bad Request error when name is empty
if (name.length == 0)
throw new HTTPStatusException(HTTPStatus.badRequest, "Name parameter cannot be empty!");
// throw an HTTP Conflict error code when name is Bob
if (sicmp(name, "bob") == 0)
throw new HTTPStatusException(HTTPStatus.conflict, "Server cannot greet Bob!");
return "Hello, " ~ name ~ "!";
}
}
and your server will then respond something like:
{
"ok": false,
"error": {
"message": "Server cannot greet Bob!",
"status": 409,
"parent": "/api/something"
}
}
You can try hunt framework, sample code for Rest api:
module app.controller.myapi;
import hunt.framework;
import app.message.UserMessage;
class MyapiController : Controller
{
mixin MakeController;
#Action
JsonResponse test()
{
UserMessage user;
user.id = 1;
user.name = "MyName";
user.email = "test#domain.com";
return new JsonResponse(user);
}
}
Your response struct:
module app.message.ResultMessage;
struct UserMessage
{
int id;
string name;
string email;
}
Response result is:
[ "id": 1, "name": "MyName", "email": "test#domain.com" ]
So when you create a new lambda from scracth you get the following default index.js inline code:
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!')
};
return response;
};
This lambda is used by an api gateway endpoint. What is the proper way to return an error code if something isn't quite right? Would the gateway handle it or should I just change the status above to a new error code?
1xx (Informational): The request was received, continuing process
2xx (Successful): The request was successfully received, understood, and accepted
3xx (Redirection): Further action needs to be taken in order to complete the request
4xx (Client Error): The request contains bad syntax or cannot be fulfilled
5xx (Server Error): The server failed to fulfill an apparently valid request
This is just returning a 200 response which is a successful response. You simply just need to return the correct code depending on the implementation you have. I think That is what you are asking for.
For more information on status code you can read this Wiki
https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
But since you're asking about implementation I would do the following
exports.handler = async (event) => {
// TODO implement
let statusCode = 200;
let message = 'success';
... // do stuff
statusCode = 400 // something went wrong
const response = {
statusCode: statusCode,
body: JSON.stringify(message)
};
return response;
};
It's really all up to you based on how you want your application to scale. Just remember, never duplicate code. Keep it modular.
Additionally you can also declare this in a function
// Somewhere else
response = function (statusCode, message) {
return {
statusCode: statusCode,
body: JSON.stringify(message)
};
};
exports.handler = async (event) => {
// TODO implement
let statusCode = 200;
let message = 'success';
... // do stuff
statusCode = 400 // something went wrong
return response(statusCode, message);
};
Check out Error Handling Patterns from AWS.
It would be a good start unless you already read it.
https://aws.amazon.com/blogs/compute/error-handling-patterns-in-amazon-api-gateway-and-aws-lambda/
Essentially you need something of the following
exports.handler = (event, context, callback) => {
var myErrorObj = {
errorType : "InternalServerError",
httpStatus : 500,
requestId : context.awsRequestId,
message : "An unknown error has occurred. Please try again."
}
callback(JSON.stringify(myErrorObj));
};
I am using this Alamofire request and returning a json
let todoEndpoint: String = "https://api.abc.com/api/v3/products?pid=uid8225&format=json&&offset=0&limit=10"
Alamofire.request(todoEndpoint)
.responseJSON { response in
guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}
print(json)
}
Now i have do loop where i want to use this json value but i am getting:
error : Use of unresolved identifier json ?
do {
for (index,subJson):(String, JSON) in json {
print("Index :\(index) Title: \(subJson)" )
} catch
{
print("there was an error")
}
As mentioned :
https://github.com/Alamofire/Alamofire#response-string-handler
" the result of a request is only available inside the scope of a response closure. Any execution contingent on the response or data received from the server must be done within a response closure."
How can i use this json value outside scope of response closure ?
Can you please suggest
Is there any completion handler i need to write and how can it be done ?
Thanks
Better use the SwiftyJson for easy json parsing with Almofire like bellow :
import Alamofire
import SwiftyJSON
static func getRequest(urlString: URL?, Parameter:NSDictionary?, completion: #escaping (_ serverResponse: AnyObject?,_ error:NSError?)->()){
Alamofire.request(urlString!, parameters:nil, headers: nil).responseJSON { response in
if(response.result.value != nil){
let serverResponse = JSON(response.result.value!)
print("Array value is \(serverResponse.arrayValue)")
completion(serverResponse as AnyObject?, nil)
}
else{
completion(nil, response.result.error as NSError?)
}
}
}
You can write this function in a separate class like NetworkLayer and then call it from any class for the WebService call as given bellow :
let url = NSURL(string: yourWebServiceUrlString)
NetworkLayer.getRequest(urlString: url as URL?, Parameter: nil) { (serverResponse, error) in
if (error == nil){
print("Server response: \(serverResponse)")
}
}
Note: You can also pass the parameter dictionary in it.
I have the next function that make an http request with completionhandler and receives a json response:
func makeRequest3(request: URLRequest, completion: #escaping (JSON!)->Void){
let task = URLSession.shared.dataTask(with: request){ data, response, error in
//Code
print(data as NSData)
let json = JSON(data: data)
completion(json)
}
task.resume()
}
I need to call this function , and find the "id_token" field for save it in a variable. Im beginner and im trying this code, but i have the error "Type '()' has no subscript members"
var response2 = makeRequest3(request: request) {response in //<-`response` is inferred as `String`, with the code above.
return(response)
}
var idtoken = response2["id_token"]
How i can do it? Im using swift3.
Your makeRequest3 itself does not return value, but the response is passed to the completion handler, do all things needed for the response in the completion handler:
makeRequest3(request: request) {response in //<-`response` is inferred as `JSON`, with your `makeRequest3`.
var idtoken = response["id_token"]
//Use `idtoken` inside this closure
//...
}