My problem is that the error handler code I register does not work on all errors. In my web.xml I registered my default error handler:
<servlet>
<servlet-name>ShowErrorHandlerServlet</servlet-name>
<servlet-class>tools.ErrorHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ShowErrorHandlerServlet</servlet-name>
<url-pattern>/showerror</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/showerror</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/showerror</location>
</error-page>
<error-page>
<location>/showerror</location>
</error-page>
I am using Jersey to map rest request, so e.g. something like this:
#GET
#Path("/throwError")
public void testThrowUnauthException() {
// String s = null;
// s.toString();
// /*
// * this nullpointer exception would be caught correctly because of
// * <exception-type>java.lang.Exception</exception-type> in the web.xml
// */
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
The WebApplicationException is not caught and the default jetty 401 error page is shown:
HTTP ERROR 401
Problem accessing /v1/locations/throwError. Reason:
UNAUTHORIZED
Powered by Jetty
Registering
<error-page>
<error-code>401</error-code>
<location>/showerror</location>
</error-page>
does not work, but it works for 404 errors..
Does someone have an idea why this is happening? I need to catch all errors with the ErrorHandlerServlet to convert them to json error messages.
I think Jersey is overriding Jetty behaviour, so it is not taking the value on the XML. One way to solve it (maybe not the most elegant):
#Context HttpServletResponse res;
#GET
#Produces(MediaType.TEXT_HTML)
public String doFoo(){
try {
//Code that causes the error
} catch (Exception e) {
res.sendRedirect("/showError");
}
return "";
}
Another idea would be extending WebApplicationException and manage the responses yourself (probably the best)
Related
I am new to camel, so this may be a simple problem to solve.
I have a spring-boot application with camel components which interacts with GitLab API.
My problem is that I need to keep the endpoint URIs in camel routes encoded, for example:
from("direct:start")
.setHeader("PRIVATE-TOKEN",constant("myToken"))
.to("https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile%2Eextension/raw?ref=master")
When the route starts, the message is sent to
"https://gitlab.com/api/v4/projects/12345/repository/files/folder/file.extension/raw?ref=master"
which returns 404, because the parameter file_path has to be encoded, as said in the GitLab doc (I've cheked with a GET from curl: with the first URI a json is returned, with the second 404).
I tried to pass the last part of the URI as HTTP_QUERY, but in this case there is the "?" between it and the URI and I get 404 again:
https://gitlab.com/api/v4/projects/12345/repository/files/?folder%2Ffile%2Eextension/raw?ref=master
I tried adding the URI with the headerHTTP_URI: this time the URI is reached correctly, but I get null body instead of the json answer.
Any idea to solve this issue?
I see that you already tried using HTTP_URI header. How did you set it? Try this:
from("direct:start")
.setHeader("PRIVATE-TOKEN", constant("myToken"))
.setHeader(Exchange.HTTP_URI, simple("https://gitlab.com/api/v4/projects/12345/repository/files/folder%2Ffile%2Eextension/raw?ref=master"))
.to("http:dummy");
This way you set the URI during the route execution, not in endpoint definition. According to docs:
Exchange.HTTP_URI: URI to call. Will override existing URI set directly on the endpoint. This URI is the URI of the HTTP server to call. Its not the same as the Camel endpoint URI, where you can configure endpoint options such as security etc. This header does not support that, its only the URI of the HTTP server.
Don't forget the dependency:
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-http</artifactId>
</dependency>
The test:
#Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:start")
.setHeader("PRIVATE-TOKEN", constant("myToken"))
.setHeader(Exchange.HTTP_URI, simple("http://0.0.0.0:8080?param=folder%2Ffile%2Eextension/raw&ref=master"))
.to("http:dummy");
from("jetty:http://0.0.0.0:8080?matchOnUriPrefix=true")
.setBody(constant("{ key: value }"))
.setHeader(Exchange.CONTENT_TYPE, constant(MediaType.APPLICATION_JSON_VALUE))
.to("mock:result");
}
};
}
#Test
public void test() throws InterruptedException {
getMockEndpoint("mock:result").expectedHeaderReceived(Exchange.HTTP_QUERY, "param=folder%2Ffile%2Eextension/raw&ref=master");
final Exchange response = template.send("direct:start", new Processor() {
public void process(Exchange exchange) throws Exception {
// nothing
}
});
assertThat(response, notNullValue());
assertThat(response.getIn().getHeader(Exchange.HTTP_URI).toString(), containsString("folder%2Ffile%2"));
assertThat(response.getOut().getBody(String.class), containsString("{ key: value }"));
assertMockEndpointsSatisfied();
}
I tried adding the URI with the headerHTTP_URI: this time the URI is reached correctly, but I get null body instead of the json answer.
Keep in mind that the response should be stored at the OUT body:
Camel will store the HTTP response from the external server on the OUT body. All headers from the IN message will be copied to the OUT message, so headers are preserved during routing. Additionally Camel will add the HTTP response headers as well to the OUT message headers.
Whenever there is normal flow in my Camel Routes I am able to get the body in the next component. But whenever there is an exception(Http 401 or 500) I am unable to get the exception body. I just get a java exception in my server logs.
I have also tried onException().. Using that the flow goes into it on error, but still I do not get the error response body that was sent by the web service(which I get when using POSTMAN directly), I only get the request in the body that I had sent to the web service.
Also adding the route:
from("direct:contractUpdateAds")
.to("log:inside_direct:contractUpdateAds_route_CompleteLog?level=INFO&showAll=true&multiline=true")
.streamCaching()
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.setHeader(Exchange.CONTENT_TYPE, constant("application/json"))
.log("before calling ADS for ContractUpdate:\nBody:${body}")
.to("{{AdsContractUpdateEndpoint}}")
.log("after calling ADS for ContractUpdate:\nBody:${body}")
.convertBodyTo(String.class)
.end();
Option 1: handle failure status codes yourself
The throwExceptionOnFailure=false endpoint option (available at least for camel-http and camel-http4 endpoints) is probably what you want. With this option, camel-http will no longer consider an HTTP Status >= 300 as an error, and will let you decide what to do - including processing the response body however you see fit.
Something along those lines should work :
from("...")
.to("http://{{hostName}}?throwExceptionOnFailure=false")
.choice()
.when(header(Exchange.HTTP_RESPONSE_CODE).isLessThan(300))
// HTTP status < 300
.to("...")
.otherwise()
// HTTP status >= 300 : would throw an exception if we had "throwExceptionOnFailure=true"
.log("Error response: ${body}")
.to("...");
This is an interesting approach if you want to have special handling for certains status codes for example. Note that the logic can be reused in several routes by using direct endpoints, just like any other piece of Camel route logic.
Option 2 : Access the HttpOperationFailedException in the onException
If you want to keep the default error handling, but you want to access the response body in the exception handling code for some reason, you just need to access the responseBody property on the HttpOperationFailedException.
Here's an example:
onException(HttpOperationFailedException.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// e won't be null because we only catch HttpOperationFailedException;
// otherwise, we'd need to check for null.
final HttpOperationFailedException e =
exchange.getProperty(Exchange.EXCEPTION_CAUGHT, HttpOperationFailedException.class);
// Do something with the responseBody
final String responseBody = e.getResponseBody();
}
});
My class in the project is under a package com.project.controller and the name of the servlet class is UpdateDatabaseController. The corresponding code of the web.xml is,
<servlet>
<servlet-name>UpdateDatabaseController</servlet-name>
<servlet-class>com.project.controller.UpdateDatabaseController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UpdateDatabaseController</servlet-name>
<url-pattern>/UpdateDB</url-pattern>
</servlet-mapping>
In the html page, I have a button that is calling a function, updateDatabase(). And the code of the script is:
$scope.updateDatabase = function(){
$http.get('/UpdateDB').success(function(data) {
alert("Controller hit");
});
};
Whenever I hit the button, the error in the browser log is displayed, that :
"NetworkError: 404 Not Found - http://localhost:8080/UpdateDB"
I want that on my button click, the POST method of the servlet class is invoked. But the error is fixed in the browser log. I am unable to solve this situation.
$scope.updateDatabase = function(){
$http.get('http://localhost/YourProject/UpdateDB').success(function(data) {
alert("Controller hit");
});
};
I had the same problem, and fixed it by adding http:// in front of the link.
you miss ProjectName in ur URL localhost:8080/ProjectName/UpdateDB
I've got some Objectify test code running in JUnit and I'm getting this error:
java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method.
at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
at com.googlecode.objectify.impl.ref.LiveRef.<init>(LiveRef.java:31)
at com.googlecode.objectify.Ref.create(Ref.java:26)
at com.googlecode.objectify.Ref.create(Ref.java:32)
at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImpl.create(DownloadTaskRepositoryImpl.java:35)
at com.netbase.followerdownloader.repository.DownloadTaskRepositoryImplTest.setUp(DownloadTaskRepositoryImplTest.java:45)
How do I resolve this for test code?
Jeff Schnitzer answered this here: https://groups.google.com/forum/#!topic/objectify-appengine/8HinahG7irg. That link points to https://groups.google.com/forum/#!topic/objectify-appengine/O4FHC_i7EGk where Jeff suggests the following quick and dirty workaround:
My #BeforeMethod starts an objectify context (ObjectifyService.begin())
My #AfterMethod closes the objectify context
Jeff suggests we use ObjectifyService.run() instead but admits it's more work.
Here's how my implementation looks:
public class DownloadTaskRepositoryImplTest {
// maximum eventual consistency (see https://cloud.google.com/appengine/docs/java/tools/localunittesting)
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
.setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
private Closeable closeable;
#Before
public void setUp() {
helper.setUp();
ObjectifyRegistrar.registerDataModel();
closeable = ObjectifyService.begin();
}
#After
public void tearDown() {
closeable.close();
helper.tearDown();
}
I also had this issue and noticed that I had not added the ObjectifyFilter to my web.xml
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
I also had to include Objectify and guava jars in my WEB-INF>lib directory and include them in my build path.
I was facing the same error and this solusion worked for me
I have an app based on Endpoints that uses Objectify. When I leave it with the default/automatic scaling, everything works great. Once I enable basic scaling, though, I get the following exception when executing the endpoint method:
[INFO] java.lang.IllegalStateException: You have not started an Objectify context. You are probably missing the ObjectifyFilter. If you are not running in the context of an http request, see the ObjectifyService.run() method.
[INFO] at com.googlecode.objectify.ObjectifyService.ofy(ObjectifyService.java:44)
[INFO] at com.myco.myapp.dao.datastore.OfyService.ofy(OfyService.java:62)
The good news is that this goes away when you enable RequestDispatcher
support in the web.xml file like so. I think this is a documentation
issue, then, but I didn't know if everyone would agree if I edited the
Wiki page directly. Here is the proposed web.xml entry, which worked
for me:
<filter>
<filter-name>ObjectifyFilter</filter-name>
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ObjectifyFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Improving michael-osofsky answer, I add this to my ofy helper class
public static void registerDataModel() {
try {
factory().register(Profile.class);
} catch (Exception e){
e.printStackTrace();
}
}
and remplace
ObjectifyRegistrar.registerDataModel();
for this
OfyService.registerDataModel();
OfyService.java
public static void registerDataModel() {
try {
factory().register(Profile.class);
} catch (Exception e){
e.printStackTrace();
}
}
As Jeff Schnitzer says in the link provided by Michael Osofsky:
In your tests you should have some notion of a 'request' even if it is just conceptual. If "each test is a request by itself", then you can use #Before/#After in conjunction with ObjectifyService.begin() to demarcate the requests. However, this is probably not actually how your tests work - it isn't how my tests work.
He then goes on to say:
This would be prettier with JDK8 closures but the idea is straightforward - you're wrapping some unit of work in a context which represents a request. It would probably be smart to add even more context like authentication in that wrapper too.
I came up with the following implementation of his idea. With the solution below, you can ensure each call to a servlet handler gets a fresh Objectify session while still making your servlet handler calls in a single line of code. It also decouples your tests from explicitly worrying about Objectify, and allows you to add additional non-Objectify context around your servlet handlers.
My solution below works with Objectify 5.1.22. I tried using Objectify 6+, but I had problems that seem to be related to this.
First, define a custom Supplier that is able to capture the exceptions thrown by a servlet handler.
#FunctionalInterface
public interface ServletSupplier<T> {
T get()
throws ServletException, IOException;
}
Next, define a wrapper method that accepts your new custom Supplier as an input, and wrap the call to ServletSupplier.get() in a try-with-resources block that calls ObjectifyService.begin(). You must also register your entity classes before calling ServletSupplier.get().
public <T> T runInServletContext(ServletSupplier<T> servletMethod)
throws ServletException, IOException {
try (Closeable session = ObjectifyService.begin()) {
ObjectifyService.register(MyObj.class);
return servletMethod.get();
}
}
Finally, anywhere in your tests that you call the servlet handler you should do so using the wrapper method.
MyObj myObjPost = runInServletContext(() -> getServlet().doPost(request, response));
// Assert results of doPost call.
MyObj myObjGet = runInServletContext(() -> getServlet().doGet(request, response));
// Assert results of doGet call.
Just in case someone ends up here (as I originally did) looking up the same problem but for the ktor "main.kt" server instead of unit tests...
After looking at the ObjectifyFilter source code, I added
val closer = ObjectifyService.begin()
... real service here ...
closer.close()
around my actual servlet code and that fixed the problem.
I've generated a Google Endpoint AppEngine project in Eclipse by using the Generate AppEngine BackEnd as described in this blog post. What that post does not describe however, and which the official Google Docs describe poorly as well, is which URL I can access that service with locally?
The service generated has one generated endpoint called DeviceInfoEndpoint. The code is shown below as well as the code in web.xml. Which URL should I access listDeviceInfo() with given that I'm hosting on port 8888 locally? I've tried the following:
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/listDeviceInfo => 404
http://localhost:8888/_ah/spi/deviceinfoendpoint/v1/listDeviceInfo => 405 GET not supported
http://localhost:8888/_ah/spi/deviceinfoendpoint/v1/DeviceInfo => 405 GET (...)
http://localhost:8888/_ah/spi/v1/deviceinfoendpoint/listDeviceInfo = > 405 GET(...)
Exerpt of DeviceInfoEndpoint.java:
#Api(name = "deviceinfoendpoint")
public class DeviceInfoEndpoint {
/**
* This method lists all the entities inserted in datastore.
* It uses HTTP GET method.
*
* #return List of all entities persisted.
*/
#SuppressWarnings({ "cast", "unchecked" })
public List<DeviceInfo> listDeviceInfo() {
EntityManager mgr = getEntityManager();
List<DeviceInfo> result = new ArrayList<DeviceInfo>();
try {
Query query = mgr
.createQuery("select from DeviceInfo as DeviceInfo");
for (Object obj : (List<Object>) query.getResultList()) {
result.add(((DeviceInfo) obj));
}
} finally {
mgr.close();
}
return result;
}
}
Web.xml:
<?xml version="1.0" encoding="utf-8" standalone="no"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.5" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>SystemServiceServlet</servlet-name>
<servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.example.dummyandroidapp.DeviceInfoEndpoint</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SystemServiceServlet</servlet-name>
<url-pattern>/_ah/spi/*</url-pattern>
</servlet-mapping>
</web-app>
API request paths should generally conform to the following:
http(s)://{API_HOST}:{PORT}/_ah/api/{API_NAME}/{VERSION}/
If you're interested in fetching/updating/deleting a specific resource, add an ID to the end. In your example, that suggests you should be querying:
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/
(which maps to list when you're making a GET request).
In general, the APIs Explorer available at /_ah/_api/explorer makes it easy to discover and query these URLs.
You can controle the path by use:
#ApiMethod(path="listDeviceInfo", httpMethod = HttpMethod.GET)
public List<DeviceInfo> listDeviceInfo(){
//... definition
}
Then you can call that from you client as:
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/listDeviceInfo
If you like send parameters then:
#ApiMethod(path="listDeviceInfo", httpMethod = HttpMethod.GET)
public List<DeviceInfo> listDeviceInfo(#Named("info") String info){
//... definition
}
http://localhost:8888/_ah/api/deviceinfoendpoint/v1/listDeviceInfo?info=holamundo