UriMatcher equivalent helper for web apps? - google-app-engine

I have coded a lot of Android App and many times, I have used the UriMatcher class to match both conetent uris and http urls.
Now I am working on a little web app using Java and Gae. I have little servlet that need to match calls agains DYNAMIC info. Not static url patterns, but runtime parse data.
In Android, UriMatcher would have been my chioce, but what is available in the Java Se World?

You can simply use Java Servlet API on GAE and register it to receive a wildcard Uris, then resolve and map Uris:
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String uri = req.getRequestURI();
// do something with the uri here
// generate a response
resp.setContentType("text/plain");
resp.getWriter().println("Hello, world");
}
}
and register it:
<servlet>
<servlet-name>My servlet</servlet-name>
<servlet-class>com.package.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>My servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

Related

Migrating to Cloud Endpoints 2.0 - Working on localhost but not on App Engine

I'm migration my GAE Java app to Google Cloud Endpoints 2.0. I followed the migration guide (https://cloud.google.com/endpoints/docs/frameworks/legacy/v1/java/migrating) to migrate to Endpoints v2.0.
The endpoints service calls are working on localhost but returning 404 (Not Found) when uploaded to the App Engine. I'm using Guice.
The relevant section from my web.xml looks similar to this:
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>com.xyz.myapp.server.guice.MyAppGuiceServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>EndpointsServlet</servlet-name>
<servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.xyz.myapp.server.endpoints.MyEP1,com.xyz.myapp.server.endpoints.MyEP2</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>EndpointsServlet</servlet-name>
<url-pattern>/_ah/api/*</url-pattern>
</servlet-mapping>
MyAppGuiceServletContextListener.java
public class MyAppGuiceServletContextListener extends GuiceServletContextListener {
#Override
protected Injector getInjector() {
return Guice.createInjector(new GuiceServletModule(), new EPServletModule());
}
}
EPServletModule.java
public class EPServletModule extends EndpointsModule {
#Override
protected void configureServlets() {
super.configureServlets();
Set<Class<?>> serviceClasses = new HashSet<Class<?>>();
serviceClasses.add(MyEP1.class);
serviceClasses.add(MyEP2.class);
configureEndpoints("/_ah/api/*", serviceClasses);
}
}
GuiceServletModule:
public class GuiceServletModule extends ServletModule {
#Override
protected void configureServlets() {
super.configureServlets();
serve("/myapp/servlet1").with(Servlet1.class);
serve("/myapp/servlet2").with(Servlet2.class);
}
}
I'm able to invoke the regular servlets at the paths:
https://[version]-dot-myapp-id.appspot.com/myapp/servlet1
https://[version]-dot-myapp-id.appspot.com/myapp/servlet2
But I'm not able to access the endpoints. It always returns 404 error code. I tried via my Javascript client and also via the APIs Explorer, and get the same error.
I also checked the logs and strangely the logs show the POST request like this:
"POST /_ah/spi/com.xyz.myapp.server.endpoints.MyEP1.myEPMethod HTTP/1.1" 404
Why does it start with /_ah/spi/ when my client is invoking it via /_ah/api/ ?
NOTE: I'm able to invoke the Endpoints 1.0, which are deployed on a different version of the same app, without any issue. But the Endpoints 2.0 version is not working.
Am I missing something?
I also have a very basic question. My client is Javascript based. Does it really make use of the Discovery Document?
I fixed this by Updating Android Studio to the latest version and updating the SDK.

App Engine: Empty referrer error from cron Job when calling Endpoint with restricted API key

I have an App engine app + Cloud endpoints. I have configured a cron task in the task queue to call one of the endpoints. The cron has an auth-constraint to admin.
All of this is working, however when I restrict the api key to certain domains, I get the following error when the cron is run:
Failed
check_errors {
code: REFERER_BLOCKED
detail: "Requests from referer <empty> are blocked."
}
It doesn't seem like I can add a referee header to the cron.yaml
apparently Google App Engine issues cron requests from the IP address 0.1.0.1.
so I could potentially allow that ip, but I want to restrict api key by domain not i.p. and it doesn't seem like I can do both
Does anyone know a workaround to allow the cron job access to an api key restricted by domain?
I found a work around for this:
Note: I see people referencing this in the docs:
"Calling Google Cloud Endpoints
You cannot call a Google Cloud Endpoint from a cron job. Instead, you should issue a request to a target that is served by a handler that's specified in your app's configuration file or in a dispatch file. That handler then calls the appropriate endpoint class and method."
https://cloud.google.com/appengine/docs/standard/java/config/cron#
Without further explanation or example that I could see.
I am however able to call my endpoints from a cron job, and it was working fine, other than the api key restraint issue.
I read several comments on other posts that mention doing a servlet mapping, but without providing an example, so here is the workaround I found and example code for the servlet mapping.
Java Class
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
#Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
// call your Endpoint Method here, or whatever you want
resp.setContentType("text/plain");
resp.getWriter().println("Hello, world");
}
}
web.xml
<servlet>
<servlet-name>cronServlet</servlet-name>
<servlet-class>com.example.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>cronServlet</servlet-name>
<url-pattern>/cronServlet</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>cronServletConstraint</web-resource-name>
<url-pattern>/cronServlet</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
cron.yaml
cron:
- description: myCron
url: /cronServlet
schedule: every 12 hours
https://cloud.google.com/appengine/docs/flexible/java/how-requests-are-handled

Warm up requests not working in Cloud Endpoints Objectify

I've been trying to get warm up requests to work in my Endpoints project using Objectify but nothing seems to be working. Is there something I missed? I tried two methods:
servlet:
public class WarmUpServ extends HttpServlet {
static {
ObjectifyService.factory().register(CounterEnt.class);
ObjectifyService.factory().register(CounterShard.class);
}
#Override
public void init() throws ServletException {
super.init();
}
#Override
public void doGet(HttpServletRequest req, HttpServletResponse res)throws ServletException,IOException {
}
#Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
web.xml
<servlet>
<servlet-name>warm-up</servlet-name>
<servlet-class>com.myapp.backend.WarmUpServ</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>warm-up</servlet-name>
<url-pattern>/war-up</url-pattern>
</servlet-mapping>
And I also tried a listener:
public class WarmUpServListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
ObjectifyService.factory().register(CounterEnt.class);
ObjectifyService.factory().register(CounterShard.class);
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
web.xml
<listener>
<listener-class>com.myapp.backend.WarmUpServListener</listener-class>
</listener>
Note: I need to register my entities this way because I have a dependency where it uses ObjectifyService directly.
Warmup requests are not guaranteed to be made.
https://cloud.google.com/appengine/docs/standard/java/warmup-requests/
If warmup requests are enabled for your application, App Engine
attempts to detect when your application needs a new instance and
initiates a warmup request to initialize a new instance. However,
these detection attempts do not work in every case. As a result, you
might encounter loading requests, even if warmup requests are enabled
in your app. For example, if your app is serving no traffic, the first
request to the app will always be a loading request, not a warmup
request.
Use a ServletContextListener instead; that will always be called once at each instance start.

Servlet-Filter is not honoured for welcome file

I am using a Filter do generate dynamicly content to be visible for webcrawlers (https://developers.google.com/webmasters/ajax-crawling/docs/specification). This filter is working fine if the incoming url contains a path (http://www.unclestock.com/app.jsp#!s=GOOG). If the incoming url contains just my domain (and a fragment), say http://www.unclestock.com#!s=GOOG, the welcome file (app.jsp) is returned, but the filter is not honnoured.
My web.xml contains the following filter map:
<filter-mapping>
<filter-name>crawler</filter-name>
<url-pattern>/app.jsp</url-pattern>
</filter-mapping>
<welcome-file-list>
<welcome-file>app.jsp</welcome-file>
</welcome-file-list>
I have tried to use an index.html welcome file instead, which redirects to app.jsp. The filter is then executed. However, this does not solve my problem: A client side redirect is not followed by the crawlers (which is the idea), and with server side redirect, I would loose my url fragment (which I also need).
Do you see any alternative solution?
I'm using Google Appengine.
I solved it by using a welcome servlet which does a RequestDispatcher forward. Note that the dispatcher FORWARD must be added to the filter-mapping in order to have the filter working during the foward.
web.xml:
<filter-mapping>
<filter-name>crawler</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
<welcome-file-list>
<welcome-file>welcome</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>welcome</servlet-name>
<servlet-class>Welcome</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>welcome</servlet-name>
<url-pattern>/welcome</url-pattern>
</servlet-mapping>
Welcome.java:
public class Welcome extends RemoteServiceServlet {
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
RequestDispatcher rd = req.getRequestDispatcher("app.jsp");
rd.forward(req, resp);
}
}

redirect through web.xml in google-app-engine

After migrating to the new HRD store, I want to redirect requests to my old application to the new HRD application. I know I should let the Google migration tool make an alias, but since I migrate an intermediate copy of my app (because of the nightmares that migrating the database causes) that is not an option.
My plan was to use a servlet that does a HTTP 301 (HttpServletResponse.SC_MOVED_PERMANENTLY) redirect, and use a servlet-mapping with /* in web.xml.
This works locally, but on the real app-engine, it doesn't. It seems like there is no URL pattern that the app-engine correctly recognizes. So far I have:
<servlet-mapping>
<servlet-name>RedirectToHRD</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>RedirectToHRD</servlet-name>
<url-pattern>/*</url-pattern>
<url-pattern>/*/*</url-pattern>
<url-pattern>/*/*/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>RedirectToHRD</servlet-name>
<url-pattern>*.jsp</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>RedirectToHRD</servlet-name>
<url-pattern>index.jsp</url-pattern>
</servlet-mapping>
I know it looks crazy, but I was desperate. Only '/' and 'index.jsp' get redirected, through the RedirectToHRD servlet. For other pages (JSP or anything else) this does not work. The log file just happily indicates that the pages get served.
Can anyone tell me what is happening?
Edit:
I did what Peter Knego kindly suggested below, and made a filter. Now my web.xml has:
<filter-mapping>
<filter-name>RedirectToHRDFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
This still does not work on the 'real' appengine, and like the earlier attempt it does work locally.
My filter has the following method:
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)resp;
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
String redirectURL = "http://fit20app-hrd.appspot.com"+request.getRequestURI();
if (request.getQueryString() != null) {
redirectURL += "?"+request.getQueryString();
}
response.setHeader("Location", redirectURL);
}
Now I am thinking that this filter may be broken, even though it works locally. On Google's servers it works for / and /index.jsp, but not for anything else.
This is solved, see comment below.
You can not have multiple <url-pattern> inside a <servlet-mapping>. Instead create multiple <servlet-mapping> with each having one <url-pattern> element.
Also, since you are trying to redirect everything you should use a servlet filter instead.

Resources