Been running myself crazy trying to solve this, very short this is the code I use to execute a call towards my REST endpoint:
String url = "http://localhost:5000/getMyObject";
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(factory);
String result = restTemplate.getForObject(url, String.class);
But no matter what headers I add it always ends up with connection refused.
Snippet of Stacktrace:
Caused by: java.net.ConnectException: Connection refused: connect
at java.base/java.net.PlainSocketImpl.waitForConnect(Native Method)
...
org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:776)
... 74 more
Setup: Test running on Windows, Application running in WSL
It works through Curl (in console and through my test)
It works if the test itself starts the application
It works through web browser
Curl -v:
localhost:5000/getMyObject
* Trying 127.0.0.1:5000
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> GET /getMyObject HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.68.0
> Accept: */*
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Vary: Origin
< Vary: Access-Control-Request-Method
< Vary: Access-Control-Request-Headers
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sat, 22 Jan 2022 10:05:31 GMT
<
* Connection #0 to host localhost left intact
{<Data>}
Something with SimpleClientHttpRequestFactory just didn't play nicely, I switched it to HttpComponentsClientHttpRequestFactory instead and now it works just fine.
String url = "http://localhost:5000/getMyObject";
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory ();
RestTemplate restTemplate = new RestTemplate(factory);
String result = restTemplate.getForObject(url, String.class);
Related
Perhaps someone here has run into this working with Payara 5.2022.3 and/or Payara 4/Jersey-2.25.1.
I've got a REST endpoint protected with BASIC auth. This was working, and then it stopped. I'm not sure what happened here, as the code didn't change. Perhaps a system update to Java?
It looks like there is a pre-flight that's not sending the BASIC auth header. If I run the same request with curl, it works fine. So, this is a Jersey client issue.
The following code is a slimmed down version, for brevity, just to give you an idea of what I'm going:
Server code:
#BasicAuthenticationMechanismDefinition(
realmName = "appSearch"
)
#DatabaseIdentityStoreDefinition(
dataSourceLookup = "jdbc/MyDB",
callerQuery = "select passhash from User where username = ?",
groupsQuery = "select groupname from Group where username = ?",
hashAlgorithm = javax.security.enterprise.identitystore.Pbkdf2PasswordHash.class,
priority = 30)
#DeclareRoles({"SOME_GROUP"})
#Singleton
#Startup
public class ApplicationSettingsBean implements ApplicationSettingsBeanLocal {
#RolesAllowed({"SOME_GROUP"})
#Path("search")
public class SearchRequestFacade {
#GET
#Path("file-number/{uuid: [a-zA-F0-9]{8}-([a-zA-F0-9]{4}-){3}[a-zA-F0-9]{12}}")
#Produces({MediaType.TEXT_PLAIN})
public Response request(
#PathParam("uuid") String uuid
) {
return Response.ok(search.fileNumber(uuid)).build();
}
}
Jersey Client (from Payara 4 application):
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(GsonProvider.GsonFeature.class);
clientConfig.property("jersey.config.server.disableMoxyJson", Boolean.TRUE);
clientConfig.property("jersey.config.client.disableMoxyJson", Boolean.TRUE);
clientConfig.property("com.sun.jersey.api.json.POJOMappingFeature", Boolean.TRUE);
clientConfig.register(HttpAuthenticationFeature.basicBuilder().nonPreemptive().credentials(username, password).build());
clientConfig.register(new LoggingFeature(Logger.getLogger(getClass().getName()), Level.INFO, null, null));
clientConfig.register(MultiPartFeature.class);
int timeout = Math.toIntExact(TimeUnit.SECONDS.toMillis(10));
clientConfig.property("jersey.config.client.connectTimeout", timeout);
clientConfig.property("jersey.config.client.readTimeout", timeout);
Client client = ClientBuilder.newClient(clientConfig);
WebTarget webTarget = client.target(url).path(urlPath);
Invocation.Builder invocationBuilder = webTarget.request(MediaType.TEXT_PLAIN_TYPE);
Response response = invocationBuilder.get();
String fileNumber = response.readEntity(String.class)
Jersey Client logging (notice the two requests/responses that occur as part of my single GET request - first one fails, second one succeeds. This leads me to believe it's a pre-flight happening with Jersey client, but not with curl):
INFO: 1 * Sending client request on thread http-thread-pool::http-listener-1(2)
1 > GET http://127.0.0.1:8080/app/api/v1/search/file-number/0c6c437e-e459-43d3-a8e6-942e128f2b80
1 > Accept: text/plain
INFO: 1 * Client response received on thread http-thread-pool::http-listener-1(2)
1 < 401
1 < Content-Language:
1 < Content-Length: 1076
1 < Content-Type: text/html
1 < Server: Payara Server 5.2022.3 #badassfish
1 < WWW-Authenticate: Basic realm="appSearch"
1 < X-Frame-Options: SAMEORIGIN
1 < X-Powered-By: Servlet/4.0 JSP/2.3 (Payara Server 5.2022.3 #badassfish Java/Ubuntu/11)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><title>Payara Server 5.2022.3 #badassfish - Error report</title><style type="text/css"><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 401 - Unauthorized</h1><hr/><p><b>type</b> Status report</p><p><b>message</b>Unauthorized</p><p><b>description</b>This request requires HTTP authentication.</p><hr/><h3>Payara Server 5.2022.3 #badassfish</h3></body></html>
INFO: 2 * Sending client request on thread http-thread-pool::http-listener-1(2)
2 > GET http://127.0.0.1:8080/app/api/v1/search/file-number/0c6c437e-e459-43d3-a8e6-942e128f2b80
2 > Accept: text/plain
2 > Authorization: Basic SOMEBASE64USERPASS
2 > User-Agent: Jersey/2.25.1 (HttpUrlConnection 1.8.0_352)
INFO: 2 * Client response received on thread http-thread-pool::http-listener-1(2)
2 < 200
2 < Content-Length: 0
2 < Content-Type: text/plain
2 < Server: Payara Server 5.2022.3 #badassfish
2 < X-Frame-Options: SAMEORIGIN
2 < X-Powered-By: Servlet/4.0 JSP/2.3 (Payara Server 5.2022.3 #badassfish Java/Ubuntu/11)
1234567890
Using curl:
$ curl -k --ssl --basic -i http://127.0.0.1:8080/app/api/v1/search/file-number/FD36C98C-6A37-47FA-BBDD-3388CB79010B?user-id=test -u username:password
HTTP/1.1 200 OK
Server: Payara Server 5.2022.3 #badassfish
X-Powered-By: Servlet/4.0 JSP/2.3 (Payara Server 5.2022.3 #badassfish Java/Ubuntu/11)
Content-Type: text/plain
Content-Length: 0
X-Frame-Options: SAMEORIGIN
1234567890
It appears that the problem was how the HttpAuthenticationFeature was constructed. Adding .nonPreemptive() caused a two-part request, where the BASIC auth header was only sent when it failed with a 401 on the first attempt. Although removing this fixed the request issue, leaving it in place should have worked, too, as the second attempt succeeded in the single request. Perhaps this is a bug with Jersey client.
Old:
HttpAuthenticationFeature.basicBuilder().nonPreemptive().credentials(username, password).build();
New:
HttpAuthenticationFeature.basicBuilder().credentials(username, password).build();
Sauce: https://web.archive.org/web/20230204034859/https://howtodoinjava.com/jersey/jersey-rest-client-authentication/
I'm using Jetty to deploy a WebSocket and an Angular application. On development it's all working but on production I have the issue that when refreshing the frontend or typing an url I get a 404 from the server that the given resource does not exist. Now I'm trying to create a rewrite rule to redirect requests to my index.html. Initializing my server looks like this:
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(config.getServer().getPort());
server.addConnector(connector);
RewriteHandler rewriteHandler = new RewriteHandler();
rewriteHandler.setRewriteRequestURI(true);
rewriteHandler.setRewritePathInfo(false);
rewriteHandler.setOriginalPathAttribute("requestedPath");
/*
RedirectRegexRule rule1 = new RedirectRegexRule();
rule1.setRegex("/(.+)");
rule1.setLocation("/index.html");
rewriteHandler.addRule(rule1);
*/
URL webRootLocation = this.getClass().getResource("/frontend/index.html");
if (webRootLocation == null)
throw new IllegalStateException("Unable to determine webroot URL location");
URI webRootUri = URI.create(webRootLocation.toURI().toASCIIString().replaceFirst("/index.html$","/"));
logger.debug("Web Root URI: {}",webRootUri);
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
contextHandler.setBaseResource(Resource.newResource(webRootUri));
contextHandler.setWelcomeFiles(new String[]{ "index.html" });
rewriteHandler.setHandler(contextHandler);
ServerContainer container = WebSocketServerContainerInitializer.initialize(contextHandler);
List<Class<? extends Encoder>> encoders = new ArrayList<>();
encoders.add(MessageEncoder.class);
List<Class<? extends Decoder>> decoders = new ArrayList<>();
decoders.add(MessageDecoder.class);
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder
.create(AppEndpoint.class, "/wss-test")
.encoders(encoders)
.decoders(decoders)
.configurator(new AppEndpointConfig(config, factory))
.build();
container.addEndpoint(endpointConfig);
//server.setHandler(contextHandler);
HandlerList handlerList = new HandlerList();
handlerList.setHandlers(new Handler[]{rewriteHandler, contextHandler});
server.setHandler(handlerList);
contextHandler.addServlet(DefaultServlet.class, "/");
server.start();
// server.dump(System.err);
server.join();
My Angular frontend is compiled in a resource folder of the application which is served by the server on - for example: localhost:8080/. If my app routs to localhost:8080/some/path everything is fine, but when refreshing the page I get a 404 that some/path is unknown. Using the rewirte rule (commented out) I get ERR_TOO_MANY_REDIRECTIONS also lazy-loaded resources / modules are not found. Any suggestions on how to use Jetty rewrite for an Angular application as I have no glue on how to continue here or does Jetty not support something like the apache rewrite that is suggested by angular deployment
RewriteEngine On
# If an existing asset or directory is requested go to it as it is
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
RewriteRule ^ - [L]
# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html
For anyone looking for a solution for this I made it work with Jettys RewriteHandler. My servers setup now looks like the following:
server = new Server(config.getServer().getPort());
RewriteHandler rewriteHandler = new RewriteHandler();
rewriteHandler.setRewriteRequestURI(true);
rewriteHandler.setRewritePathInfo(false);
rewriteHandler.setOriginalPathAttribute("requestedPath");
RewriteRegexRule rule1 = new RewriteRegexRule();
rule1.setRegex("^((?!"+wsPath+"|\\.js|\\.css|\\.jpe?g|\\.png).)*$");
rule1.setReplacement("/index.html");
rewriteHandler.addRule(rule1);
URL webRootLocation = this.getClass().getResource("/frontend/index.html");
if (webRootLocation == null)
throw new IllegalStateException("Unable to determine Web-Root URL location");
URI webRootUri = URI.create(webRootLocation.toURI().toASCIIString().replaceFirst("/index.html$","/"));
logger.debug("Web-Root URI: {}",webRootUri);
ServletContextHandler contextHandler = new ServletContextHandler(ServletContextHandler.SESSIONS);
contextHandler.setContextPath("/");
contextHandler.setBaseResource(Resource.newResource(webRootUri));
contextHandler.setWelcomeFiles(new String[]{ "index.html" });
rewriteHandler.setHandler(contextHandler);
// Initialize WebSocket
ServerContainer container = WebSocketServerContainerInitializer.initialize(contextHandler);
List<Class<? extends Encoder>> encoders = new ArrayList<>();
encoders.add(MessageEncoder.class);
List<Class<? extends Decoder>> decoders = new ArrayList<>();
decoders.add(MessageDecoder.class);
ServerEndpointConfig endpointConfig = ServerEndpointConfig.Builder
.create(AppEndpoint.class, "/" + wsPath)
.encoders(encoders)
.decoders(decoders)
.configurator(new AppEndpointConfig(config, factory))
.build();
container.addEndpoint(endpointConfig);
server.setHandler(rewriteHandler);
contextHandler.addServlet(DefaultServlet.class, "/");
server.start();
server.join();
It's not ideal as only specific file endings match the regex but it does work with a builded and deployed Angular application inside of Jetty. If a RESTful API is used some regex must be defined to match a path of the API like the wsPath (a simple string) in my example.
The only way to accomplish this in the Servlet world is to have a error page handling the 404 status code and issuing the redirect yourself.
This can only be done within a specific webapp, and not from a generic rewrite handling routine.
Why?
Well, the "If the requested resource doesn't exist, use index.html" is the key.
What happens.
The webapp was entered, and no url-pattern matched, so the default url-pattern (of "/") is used.
The default url-pattern is mapped to what's known as "The Default Servlet"
The Default Servlet is responsible for looking in the webapp's base resource for a matching resource and returning that content as a static resource request.
If the static resource doesn't exist, and the request was for a directory (eg: /js/ then use the list of welcome files to look up a welcome file. (this list is configured in the WEB-INF/web.xml)
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
If the static resource still doesn't exist, process it as a 404 response.
Meanwhile, the error page handling routines of the servlet spec kick in.
This will result in a lookup for a path declared for 404 in your WEB-INF/web.xml.
<servlet>
<servletname>404Handler</servlet-name>
<servlet-class>com.acme.My404Servlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>404Handler</servlet-name>
<url-pattern>/404status</url-pattern>
</servlet-mapping>
<!-- ... then later ... -->
<error-page>
<error-code>404</error-code>
<location>/404status</location>
</error-page>
That path can be a servlet, a static resource, a jsp, etc. Pretty much anything that you can reference with a path.
If it's a servlet (or jsp) you can interrogate the original request via the request attributes to know why you are handling this error.
See: https://stackoverflow.com/a/32910916/775715
An example of this in embedded-jetty would be ...
package jetty.errors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.resource.PathResource;
public class EmbeddedWelcomeErrorDemo
{
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
String baseDir = System.getProperty("user.home");
if (args.length > 0)
baseDir = args[0];
Path basePath = Paths.get(baseDir);
if (!Files.exists(basePath) || !Files.isDirectory(basePath))
{
throw new IOException("Not a valid directory: " + basePath);
}
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.setBaseResource(new PathResource(basePath));
context.setWelcomeFiles(new String[]{
"index.html"
});
// Add error page mapping for context
context.addServlet(ErrorHandling.class, "/errorpage");
ErrorPageErrorHandler errorMapper = new ErrorPageErrorHandler();
errorMapper.addErrorPage(404, "/errorpage");
context.setErrorHandler(errorMapper);
// to handle static resources against base resource (always last)
// always named "default" (per spec)
ServletHolder defaultHolder = new ServletHolder("default", DefaultServlet.class);
// assigned to default url-pattern of "/" (per spec)
context.addServlet(defaultHolder, "/");
HandlerList handlers = new HandlerList();
handlers.addHandler(context);
handlers.addHandler(new DefaultHandler()); // for non-context errors
server.setHandler(handlers);
server.start();
server.join();
}
public static class ErrorHandling extends HttpServlet
{
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getDispatcherType() != DispatcherType.ERROR)
{
// we didn't get here from a error dispatch.
// somebody attempted to use this servlet directly.
resp.setStatus(404);
return;
}
String requestedResource = (String)req.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
log("[ErrorHandling] Requested resource was " + requestedResource);
int statusCode = (int)req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
switch (statusCode)
{
case 404:
// let handle it by a redirect
resp.sendRedirect("/");
break;
default:
// pass the other errors through
resp.setStatus(statusCode);
break;
}
}
}
}
Some example of what happens.
$ mkdir $HOME/tmp-base
$ mdkir css
$ echo "this is the index.html" > index.html
$ echo "this is my other html" > myother.html
$ echo "this is my fancy css" > css/main.css
Then run the server example with the command line to this directory
$ java ... jetty.errors.EmbeddedWelcomeErrorDemo $HOME/tmp-base
2019-09-24 14:17:55.540:INFO::main: Logging initialized #190ms to org.eclipse.jetty.util.log.StdErrLog
2019-09-24 14:17:55.621:INFO:oejs.Server:main: jetty-9.4.20.v20190813; built: 2019-08-13T21:28:18.144Z; git: 84700530e645e812b336747464d6fbbf370c9a20; jvm 1.8.0_202-b08
2019-09-24 14:17:55.661:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler#7921b0a2{/,file:///home/joakim/tmp-base/,AVAILABLE}
2019-09-24 14:17:55.674:INFO:oejs.AbstractConnector:main: Started ServerConnector#7cef4e59{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2019-09-24 14:17:55.674:INFO:oejs.Server:main: Started #325ms
And then make a few requests ...
$ curl -L -vv http://localhost:8080/
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:26:28 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
this was welcome file handling
$ curl -L -vv http://localhost:8080/myother.html
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /myother.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:21:10 GMT
< Last-Modified: Tue, 24 Sep 2019 19:13:46 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 22
< Server: Jetty(9.4.20.v20190813)
<
This is my other html
* Connection #0 to host localhost left intact
this was normal static file serving
$ curl -L -vv http://localhost:8080/css/main.css
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /css/main.css HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:22:22 GMT
< Last-Modified: Tue, 24 Sep 2019 19:22:16 GMT
< Content-Type: text/css
< Accept-Ranges: bytes
< Content-Length: 21
< Server: Jetty(9.4.20.v20190813)
<
this is my fancy css
* Connection #0 to host localhost left intact
This was normal static file serving
If I make some requests to non-existent resources or directories ....
$ curl -L -vv http://localhost:8080/css/bogus.css
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /css/bogus.css HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 24 Sep 2019 19:22:46 GMT
< Location: http://localhost:8080/
< Content-Length: 0
< Server: Jetty(9.4.20.v20190813)
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8080/'
* Found bundle for host localhost: 0x5647e1581a50 [can pipeline]
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:22:46 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
This was handled by the ErrorHandling servlet
$ curl -L -vv http://localhost:8080/this/directory/does/not/exist
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /this/directory/does/not/exist HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 24 Sep 2019 19:23:02 GMT
< Location: http://localhost:8080/
< Content-Length: 0
< Server: Jetty(9.4.20.v20190813)
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8080/'
* Found bundle for host localhost: 0x561eefa8b020 [can pipeline]
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:23:02 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
This was handled by the ErrorHandling servlet
[joakim#hyperion tmp]$ curl -L -vv http://localhost:8080/non-existant.jpeg
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /non-existant.jpeg HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Date: Tue, 24 Sep 2019 19:21:18 GMT
< Location: http://localhost:8080/
< Content-Length: 0
< Server: Jetty(9.4.20.v20190813)
<
* Connection #0 to host localhost left intact
* Issue another request to this URL: 'http://localhost:8080/'
* Found bundle for host localhost: 0x563f476b6a50 [can pipeline]
* Re-using existing connection! (#0) with host localhost
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 24 Sep 2019 19:21:18 GMT
< Last-Modified: Tue, 24 Sep 2019 19:12:21 GMT
< Content-Type: text/html
< Accept-Ranges: bytes
< Content-Length: 23
< Server: Jetty(9.4.20.v20190813)
<
this is the index.html
* Connection #0 to host localhost left intact
This was handled by the ErrorHandling servlet
I'm am developing an application with the Jersey framework that acts as a proxy to the docker HTTP API. I'm able to upload images successfully to Docker but I am unable to forward the response from Docker to the client without an error. I believe it has something to do with the Transfer-Encoding=[chunked] header in the response.
Response
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> POST /proxy/images/load HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:8080
> Accept: */*
> Content-Type: application/octet-stream
> Content-Length: 477637632
> Expect: 100-continue
>
< HTTP/1.1 100
< HTTP/1.1 200
< Transfer-Encoding: chunked
< Docker-Experimental: false
< Api-Version: 1.26
< Server: Docker/1.13.1 (linux)
< Date: Mon, 23 Jul 2018 11:20:14 GMT
< Content-Type: application/json
< Content-Length: 54
<
* Problem (2) in the Chunked-Encoded data
* Closing connection 0
curl: (56) Problem (2) in the Chunked-Encoded data
code
#POST
#Path("/load2")
//#Consumes(MediaType.APPLICATION_OCTET_STREAM)
public Response loadimages(byte[] input, #Context UriInfo uriInfo) {
WebTarget webTarget = ClientBuilder.newClient().target("http://127.0.0.1:5555/images").path("load");
Response response = webTarget.request(MediaType.APPLICATION_JSON).post(Entity.entity(input, MediaType.APPLICATION_OCTET_STREAM));
return response;
}
I have a small demo to play with customized status code.
The interesting part is the request will always hang there if the status is below 200, like 105, 199, etc. But works for any status greater than 200, like 209, 789 etc.
Http status code registry, refer to https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
Spring boot: 1.5.4.RELEASE with embedded tomcat
Java: 8
Controller:
#RestController
public class DemoController {
#GetMapping("/hello")
public ResponseEntity get() {
return ResponseEntity.status(105).build();
}
}
Can anyone give me a clear explanation?
I create a gist at here: https://gist.github.com/pengisgood/dbea1fcdc45c2bb5809871c7f020b800
Update:
I also create a small demo to reproduce it at here:
https://github.com/pengisgood/springboot-customize-status-code
Update:
After I run curl -v localhost:8080/hello, I can see the status, but the response doesn't finish. Refer to the gif below:
I also ran into this issue and found that it is not Spring that creates this behavior. It is Tomcat.
curl -v --header "Expect: 100-continue" http://localhost:8080
Calling any configured endpoint like this will return an extra response code that doesn't terminate the request.
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0*
Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.56.1
> Accept: */*
> Expect: 100-continue
>
< HTTP/1.1 100
< HTTP/1.1 200
< Set-Cookie: JSESSIONID=9355141A10CF546E9A9A43F5A5C0B1A4; Path=/; HttpOnly
< Content-Type: text/html;charset=ISO-8859-1
< Content-Length: 58
< Date: Tue, 31 Jul 2018 17:27:52 GMT
<
{ [58 bytes data]
100 58 100 58 0 0 58 0 0:00:01 --:--:-- 0:00:01 82<html>
<body>
<h2>Hello Heroku!</h2>
</body>
</html>
* Connection #0 to host localhost left intact
note the HTTP/1.1 100
This response came from this project https://devcenter.heroku.com/articles/create-a-java-web-application-using-embedded-tomcat which doesn't have spring. If I modify the HelloServlet to include a response code of 100 it just hangs.
Looking deeper:
https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3
The specification makes it clear that the 100 response is supposed to happen in the same request. The reason it hangs is because it is expecting the client to respond with the content of the request.
Looking at the wiki for other 1XX response codes, it appears to also be true that some information is returned without closing the request. My guess is that Tomcat expects all 1xx response codes to act in this manner.
As far as I can tell the Spring DispacherServlet is handling the different return codes in exactly the same way. I think what's happening is curl is just leaving the connection open because the response is in the 1xx range.
This article provides a good primer on status codes. This sentence in particular is relevant:
100–199 Codes in the 100s are informational, indicating that the client should respond with some other action.
If you run curl with --trace you'll see that the 105 response does actually arrive:
curl -v -trace http://localhost:8080/hello
Trying ::1...
TCP_NODELAY set
Connected to localhost (::1) port 8080 (#0)
> GET /hello HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 105
< Date: Tue, 19 Sep 2017 18:07:04 GMT
^C
So what I think is happening is the response is returned, the client should respond with some other action (but doesn't) so it looks like the thing has hung.
Probably the real question here is why are you trying to return a 105 status and what do you expect to happen?
I've created a simple server Java application using Gradle. As an embedded server , I am using Jetty . I am also using a Gretty plugin as it supports the latest Jetty version.
The project runs just fine . And I have tried to stress test it. As a part of my test I need to check the response time and therefore I am sending "Connection:Close" header via curl.
My response is a long JSON string , and I see only part of it , after which the connection hangs . I would like to know Why is it happening , and how can I work around it.
NOTE :
When sending Connection:Keep-alive header , everything is fine
When response from the server is not a long string , but smaller . It works just fine (doesn't hang)
Tried the standard Jetty plugin from gradle , the result was the same.
HOW TO TEST :
Build and run my project from console ./gradlew appRun
From bash console run curl -H "Connection:Close" -i "http://localhost:8080/Environment/example"
See the partial response and the connection still alive...
Seems like you are confusing the persistent connection modes between HTTP/1.0 and HTTP/1.1.
Either that, or you are using a really old version of curl that still defaults to HTTP/1.0.
HTTP/1.0 has no persistent connections by default, so to use persistent connections we send Connection: keep-alive.
HTTP/1.1 uses persistent connections by default, so to disable it we can send Connection: close
Using HTTP/1.0, with Connection: close is like sending this ...
GET /Environment/example HTTP/1.0
Host: localhost:8080
Connection: close
... which produces an invalid header value for Connection per the HTTP/1.0 spec
Lets use the verbose features of curl to see what's really going on Connection wise...
Example: HTTP/1.1 with normal operation:
$ curl --verbose --http1.1 http://apache.org/ -so /dev/null
* Trying 88.198.26.2...
* Connected to apache.org (88.198.26.2) port 80 (#0)
> GET / HTTP/1.1
> Host: apache.org
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 06 May 2016 12:05:39 GMT
< Server: Apache/2.4.7 (Ubuntu)
< Last-Modified: Fri, 06 May 2016 11:10:20 GMT
< ETag: "cf64-5322a812896a8"
< Accept-Ranges: bytes
< Content-Length: 53092
< Vary: Accept-Encoding
< Cache-Control: max-age=3600
< Expires: Fri, 06 May 2016 13:05:39 GMT
< Content-Type: text/html
<
{ [1125 bytes data]
* Connection #0 to host apache.org left intact
Notice that it says it kept the connection intact?
Example: HTTP/1.1 with manual Connection: close operation:
$ curl --verbose --http1.1 --header "Connection: close" http://apache.org/ -so /dev/null
* Trying 140.211.11.105...
* Connected to apache.org (140.211.11.105) port 80 (#0)
> GET / HTTP/1.1
> Host: apache.org
> User-Agent: curl/7.43.0
> Accept: */*
> Connection: close
>
< HTTP/1.1 200 OK
< Date: Fri, 06 May 2016 12:06:35 GMT
< Server: Apache/2.4.7 (Ubuntu)
< Last-Modified: Fri, 06 May 2016 11:10:20 GMT
< ETag: "cf64-5322a812896a8"
< Accept-Ranges: bytes
< Content-Length: 53092
< Vary: Accept-Encoding
< Cache-Control: max-age=3600
< Expires: Fri, 06 May 2016 13:06:35 GMT
< Connection: close
< Content-Type: text/html
<
{ [1106 bytes data]
* Closing connection 0
Ah, the HTTP response headers say that the server will close, and curl saw the connection being closed. What we wanted.
Example: HTTP/1.0 with normal operation:
$ curl --verbose --http1.0 http://apache.org/ -so /dev/null
* Trying 140.211.11.105...
* Connected to apache.org (140.211.11.105) port 80 (#0)
> GET / HTTP/1.0
> Host: apache.org
> User-Agent: curl/7.43.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 06 May 2016 12:08:27 GMT
< Server: Apache/2.4.7 (Ubuntu)
< Last-Modified: Fri, 06 May 2016 11:10:20 GMT
< ETag: "cf64-5322a812896a8"
< Accept-Ranges: bytes
< Content-Length: 53092
< Vary: Accept-Encoding
< Cache-Control: max-age=3600
< Expires: Fri, 06 May 2016 13:08:27 GMT
< Connection: close
< Content-Type: text/html
<
{ [4002 bytes data]
* Closing connection 0
See how the HTTP response headers say that the server will close?
Curl also saw the connection being closed.
That's what we expect with normal HTTP/1.0 operation.
Example: HTTP/1.0 with persistent connection:
$ curl --verbose --http1.0 --header "Connection: keep-alive" http://apache.org/ -so /dev/null
* Trying 88.198.26.2...
* Connected to apache.org (88.198.26.2) port 80 (#0)
> GET / HTTP/1.0
> Host: apache.org
> User-Agent: curl/7.43.0
> Accept: */*
> Connection: keep-alive
>
< HTTP/1.1 200 OK
< Date: Fri, 06 May 2016 12:08:37 GMT
< Server: Apache/2.4.7 (Ubuntu)
< Last-Modified: Fri, 06 May 2016 11:10:20 GMT
< ETag: "cf64-5322a812896a8"
< Accept-Ranges: bytes
< Content-Length: 53092
< Vary: Accept-Encoding
< Cache-Control: max-age=3600
< Expires: Fri, 06 May 2016 13:08:37 GMT
< Keep-Alive: timeout=30, max=100
< Connection: Keep-Alive
< Content-Type: text/html
<
{ [3964 bytes data]
* Connection #0 to host apache.org left intact
Yup, the server indicates that it will use Keep-Alive too (per HTTP/1.0 spec), and curl even concurs and says the connection is left intact.