The akka-http documentation provides an example to query a http service:
http://doc.akka.io/docs/akka-http/current/scala/http/client-side/request-level.html
how can I tell akka-http to automatically follow redirects, instead of receiving a HttpResponse with code == 302?
akka 2.5.3, akka-http 10.0.9
import akka.actor.{ Actor, ActorLogging }
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.{ ActorMaterializer, ActorMaterializerSettings }
import akka.util.ByteString
class Myself extends Actor
with ActorLogging {
import akka.pattern.pipe
import context.dispatcher
final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system))
val http = Http(context.system)
override def preStart() = {
http.singleRequest(HttpRequest(uri = "http://akka.io"))
.pipeTo(self)
}
def receive = {
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
entity.dataBytes.runFold(ByteString(""))(_ ++ _).foreach { body =>
log.info("Got response, body: " + body.utf8String)
}
case resp # HttpResponse(code, _, _, _) =>
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
}
You can't tell Akka-http client to do this automatically. It is an open issue for the Akka project: https://github.com/akka/akka-http/issues/195
You could handle this manually with something like:
case resp # HttpResponse(StatusCodes.Redirection(_, _, _, _), headers, _, _) =>
//Extract Location from headers and retry request with another pipeTo
You probably would want to maintain a count of the number of times you get redirected to avoid an infinite loop.
Related
The URI myresourceUrl when hit in browser shows the json content in browser.
Requirement:
Need to use the get method of httpbuilder-ng to call GET of URI and response should have the content as json.
This json file will be necessary as input for another task.
How to achieve this. Do we need any parser to get the returned response as json using http-builder-ng.
Expected response format:
{"name":"Abc","info":{"age":45,"height":"5.5"}}
Tried the below get request using:
// setting the request URI
HttpBuilder http = HttpBuilder.configure(config -> {
config.getRequest().setUri(myresourceUrl);
});
String response = http.get(LazyMap.class, cfg -> {
cfg.getRequest().getUri().setPath(myPath);
}).toString();
Actual format we are getting:
{name:Abc,info:{age:45,height:5.5}}
How to get the response indicated above in expected response format.
First, confirm that you http request is indeed returning a JSON response. If so, you can use the gson library. Try
import com.google.code.gson;
String response = gson.toJSON(http.get(LazyMap.class, cfg -> {
cfg.getRequest().getUri().setPath(myPath);
}));
By default a content-type of "application/json" will be parsed rather than returned as a String. You need to define a custom parser for the content-type. I put together an example using a fake server that returns "application/json" content and then shows how to return it as a string in HttpBuilder-NG:
import com.stehno.ersatz.ErsatzServer;
import groovyx.net.http.HttpBuilder;
import static com.stehno.ersatz.ContentType.APPLICATION_JSON;
import static groovyx.net.http.NativeHandlers.Parsers.textToString;
public class Runner {
public static void main(final String[] args) {
final ErsatzServer server = new ErsatzServer();
server.expectations(expects -> {
expects.get("/something").responder(response -> {
response.body("{\"name\":\"Abc\",\"info\":{\"age\":45,\"height\":\"5.5\"}}", APPLICATION_JSON);
});
});
final String response = HttpBuilder.configure(cfg -> {
cfg.getRequest().setUri(server.getHttpUrl());
cfg.getResponse().parser("application/json", (chainedHttpConfig, fromServer) -> textToString(chainedHttpConfig, fromServer));
}).get(String.class, cfg -> {
cfg.getRequest().getUri().setPath("/something");
});
System.out.println(response);
System.exit(0);
}
}
Note the cfg.getResponse().parser("application/json", (chainedHttpConfig, fromServer) -> textToString(chainedHttpConfig, fromServer)); line which is where the parsing happens (overrides the default behavior) - see also the import import static groovyx.net.http.NativeHandlers.Parsers.textToString;.
ActorRef actor = ...;
Flow<HttpRequest, HttpResponse, NotUsed> flow = Flow
.of(HttpRequest.class)
.via(Flow.fromSinkAndSource(
Sink
.actorRef(actor, "COMPLETED"),
Source
.actorRef(4, OverflowStrategy.fail())
.mapMaterializedValue(sourceActor -> {
actor.tell("REGISTER", sourceActor);
return sourceActor;
})
))
.map(info -> (HttpResponse) info);
connection.handleWith(flow, materializer);
I create an actor when a connection accepted, and handle the HttpRequest with this actor.
Question(1): Is there a better way to implement this?
Question(2): Now, requests from a connection is sent to the same actor, and I can retrieve a token from a HttpRequest. How can I send requests to a specific actor base on the token? The pseudocode as follow is what I try to do.
Flow<HttpRequest, HttpResponse, NotUsed> flow = Flow
.of(HttpRequest.class)
.via(
.........................................................
String token = retrieveTokenFromHttpRequest(HttpRequest);
ActorRef actor = actorContainer.get(token);
.........................................................
Flow.fromSinkAndSource(
Sink
.actorRef(actor, "COMPLETED"),
Source
.actorRef(4, OverflowStrategy.fail())
.mapMaterializedValue(sourceActor -> {
actor.tell("REGISTER", sourceActor);
return sourceActor;
})
)
)
.map(info -> (HttpResponse) info);
connection.handleWith(flow, materializer);
I know the question has been asked before and I agree with most answers that claim it is better to follow the way requests are made async with URLSession in Swift 3. I haver the following scenario, where async request cannot be used.
With Swift 3 and the ability to run swift on servers I have the following problem.
Server Receives a request from a client
To process the request the server has to send a url request and wait for the response to arrive.
Once response arrives, process it and reply to the client
The problem arrises in step 2, where URLSession gives us the ability to initiate an async data task only. Most (if not all) server side swift web frameworks do not support async responses. When a request arrives to the server everything has to be done in a synchronous matter and at the end send the response.
The only solution I have found so far is using DispatchSemaphore (see example at the end) and I am not sure whether that will work in a scaled environment.
Any help or thoughts would be appreciated.
extension URLSession {
func synchronousDataTaskWithURL(_ url: URL) -> (Data?, URLResponse?, Error?) {
var data: Data?
var response: URLResponse?
var error: Error?
let sem = DispatchSemaphore(value: 0)
let task = self.dataTask(with: url as URL, completionHandler: {
data = $0
response = $1
error = $2 as Error?
sem.signal()
})
task.resume()
let result = sem.wait(timeout: DispatchTime.distantFuture)
switch result {
case .success:
return (data, response, error)
case .timedOut:
let error = URLSessionError(kind: URLSessionError.ErrorKind.timeout)
return (data, response, error)
}
}
}
I only have experience with kitura web framework and this is where i faced the problem. I suppose that similar problems exist in all other swift web frameworks.
In Vapor, you can use the Droplet's client to make synchronous requests.
let res = try drop.client.get("https://httpbin.org")
print(res)
Additionally, you can use the Portal class to make asynchronous tasks synchronous.
let res = try Portal.open { portal in
asyncClient.get("https://httpbin.org") { res in
portal.close(with: res)
}
}
Your three-step problem can be solved via the use of a completion handler, i.e., a callback handler a la Node.js convention:
import Foundation
import Kitura
import HeliumLogger
import LoggerAPI
let session = URLSession(configuration: URLSessionConfiguration.default)
Log.logger = HeliumLogger()
let router = Router()
router.get("/test") { req, res, next in
let datatask = session.dataTask(with: URL(string: "http://www.example.com")!) { data, urlResponse, error in
try! res.send(data: data!).end()
}
datatask.resume()
}
Kitura.addHTTPServer(onPort: 3000, with: router)
Kitura.run()
This is a quick demo of a solution to your problem, and it is by no means following best Swift/Kitura practices. But, with the use of a completion handler, I am able to have my Kitura app make an HTTP call to fetch the resource at http://www.example.com, wait for the response, and then send the result back to my app's client.
Link to the relevant API: https://developer.apple.com/reference/foundation/urlsession/1410330-datatask
Currently I'm trying to implement the "actor-per-request" pattern proposed by NET-A-PORTER devs in Akka HTTP. The problem I'm facing is that this pattern is not documented anywhere in the docs. There doesn't seem to be a way to do the following:
IO(Http) ! Http.Bind(serviceActor, "localhost", port = 38080)
How can I use one Akka actor per request without using Spray?
The HttpExt class has a method bindAndHAndleAsync that can be used for this purpose. This method takes in a function with the following signature:
handler: (HttpRequest) ⇒ Future[HttpResponse]
So, suppose we have an Actor that will produce an HttpResponse when asked about an HttpRequest:
class HttpResponseHandlerActor extends Actor {
override def receive = {
case _ : HttpRequest =>
sender() ! HttpResponse(200, entity = "Response From Actor")
}
}
Inefficient Answer
Your question explicitly asks how to use 1 Actor per request, to do that we can now use our Actor class to create a handler function:
implicit val actorSystem = ActorSystem()
implicit val timeout = Timeout(5 seconds)
val handler : (HttpRequest) => Future[HttpResponse] = (httpRequest) = {
val actorHandlerRef =
system.actorOf(Props[HttpResponseHandlerActor], "responseActor")
(actorHandlerRef ask httpRequest).mapTo[HttpResponse]
}
We can now use this function to bind our server with:
val serverBinding : Future[ServerBinding] =
Http().bindAndHandleAsync(handler, "localhost", 8080)
Efficient Answer
It is usually not necessary to re-create a new Actor for each request, typically you want to create 1 Actor and use it for every request.
Therefore we can move the Actor creation outside of handler:
val handler : (ActorRef) => (HttpRequest) => Future[HttpResponse] =
(actorRef) => (httpRequest) =>
(actorRef ask httpRequest).mapTo[HttpResponse]
The server binding is now slightly modified to:
val singleResponseActorRef =
system.actorOf(Props[HttpResponseHandlerActor], "responseActor")
val serverBinding : Future[ServerBinding] =
Http().bindAndHandleAsync(handler(singleResponseActorRef),
"localhost",
8080)
While working with javascript that uses REST services extensively -- including using vocabs like GET, PUT, POST, DELETES, etc; I have found it hard to mock the server side so front end development can go on independently (of back end).
It is also useful to sometimes capture multi-step data, so we can help reproduce the entire chain of REST even (or bugs related to the front end that are triggered from these chains)
What tools can I use to mock REST calls, esp stateful ones? (i.e. if I do a PUT on some resource, I expect the next GET on it to change somehow)
I tried SOAPUI 4.0.1 and it's REST mocking is disappointing. Plus, my need is beyond single state mocking (which anyone can do with a static .json file). I need to do state transition type of mocks; working with Content-Range headers would be best.
Anyone?
I actually ended up creating my own Java REST Mock Engine that can basically mock any response. As long as you can handcraft or cut-paste a text file that simulates the entire http response, you can use my solution to mock the service.
Here's the servlet:
package com.mockrest.debug;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Servlet implementation class MockGridData
*/
public class MockRest extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* #see HttpServlet#HttpServlet()
*/
public MockRest() {
super();
// TODO Auto-generated constructor stub
}
#Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
sub:{
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;
String setdata = request.getParameter("__setdata");
if (setdata!=null && setdata.length()>0){
System.err.println("Setting Data...");
HttpSession sess = request.getSession(true);
String data = "/"+request.getParameter("__setdata");
sess.setAttribute("data", data);
try{
InputStream is = getServletContext().getResourceAsStream(data);
if (is!=null){
is.close();
response.getWriter().write("Successfully pointed next REST call to:"+data);
}
else{
response.sendError(500, "Cannot find resource:"+data);
}
}
catch (IOException ioe){
response.sendError(500, Arrays.deepToString(ioe.getStackTrace()));
}
}
else{
System.err.println("Fetching Data...");
HttpSession sess = request.getSession(false);
if (sess==null || sess.getAttribute("data")==null){
response.sendError(500,"Session invalid or no Previous Data Set!");
}
String rsrc = (String)sess.getAttribute("data");
System.err.println("Resource Being used:"+rsrc);
InputStream is = getServletContext().getResourceAsStream(rsrc);
if (is!=null){
String statusline = readLine(is);
Pattern statusPat = Pattern.compile("^HTTP/1.1 ([0-9]+) (.*)$");
Matcher m = statusPat.matcher(statusline);
if (m!=null && m.matches()){
int status = Integer.valueOf(m.group(1));
response.setStatus(status, m.group(2));
}
else{
throw new ServletException("Bad input file: status line parsing failed, got this as status line:"+statusline);
}
String line;
Pattern httpHeaderPat = Pattern.compile("^([^:]+): (.*)$");
while ((line=readLine(is))!=null){
if (line.length()==0){
// end of headers
break;
}
Matcher m2 = httpHeaderPat.matcher(line);
if (m2!=null && m2.matches()){
response.setHeader(m2.group(1), m2.group(2));
}
}
OutputStream os = response.getOutputStream();
byte[] buf = new byte[1024];
int size;
while ((size=is.read(buf))>0){
os.write(buf, 0, size);
}
os.flush();
}
}
}
}
private String readLine(InputStream is) throws IOException {
StringBuffer sb = new StringBuffer();
char c;
while ((c=(char)is.read())!='\n'){
sb.append(c);
}
if (sb.charAt(sb.length()-1) == '\r'){
sb.deleteCharAt(sb.length()-1);
}
return sb.toString();
}
}
To configure it, place prebuilt response files inside your WebContent folder. I usually end these files with .http extensions.
An example init.http file is below. Pretend we placed this file inside a folder called data inside WebContent:
HTTP/1.1 200 OK
Date: Wed, 26 Oct 2011 18:31:45 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 4.0.30319
Content-Range: items 0-1/2
Content-Length: 385
Cache-Control: private
Content-Type: application/json
[
{
"id": "249F0",
"field1": " Global",
"displaystartdate": "2007-10-20",
"displayenddate": "2012-10-20",
"status": "Major Delay",
"children": true
},
{
"id": "962581",
"field2": "Europe",
"displaystartdate": "2007-10-20",
"displayenddate": "2012-10-20",
"status": "Major Delay",
"children": true
}
]
Headers must separate with body by an empty line (no spaces, nada). People familiar with http will notice it's a pure http response. This is on purpose.
You can use this tool to simulate any of the http headers you want the response to have; even going so far to respond with different server header(in my example, I simulated the response pretending to be IIS 6.0); or a different HTTP status code, etc.
To invoke it from your browser/javascript; first prime it with:
http://yourserver/yourweb/MockGridData?__setdata=data/init.http
Then in your javascript or REST AJAX call, if it goes to
http://yourserver/yourweb/MockGridData
with any method or parameter; it will get the http response you previously crafted with; even down to the Content-Range; Cache headers; etc. If you then need the subsequent AJAX call to return something else, simply call with __setdata again. I suggest you setup a few buttons to do the explicit state transition in your web app.
Assuming everything is setup, for a simulated REST chain, a developer may do:
invoke
http://yourserver/yourweb/MockGridData?__setdata=data/init.http
run a javascript module that will result in calling (say, with GET)
http://yourserver/yourweb/MockGridData
click a button that then does:
http://yourserver/yourweb/MockGridData?__setdata=data/step1.http
run another javascript step that will result in calling (say, with PUT)
http://yourserver/yourweb/MockGridData
click another button that then does:
http://yourserver/yourweb/MockGridData?__setdata=data/step2.http
run another javascript step that will result in calling (say, with GET)
http://yourserver/yourweb/MockGridData
but this time expecting different result than #4.
This should even work with binary and gzipped responses, but I haven't tested that.
Here is another homegrown rest-mocking tool: https://github.com/mkotsur/restito.