Testing server deployed on Google App Engine - google-app-engine

I want to test a server I have deployed on GAE to see if a connection can be made via a HTTP POST request. The end client will run on Android but for now I would like to run a simple test on my laptop.
I send different "action" params as part of the request to the server and based on the action it will look for and handle other params to complete the request. Below is an example of how a command is handled. One param is the action and the other a username. It will in the end return a JSON object with the groups this user is a member of but for now I want to just get the test string "Just a test" back to see everything is working.
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
.
.
.
.
/*
* GET GROUPS
*
* #param action == getGroups
* #param user == the username of the user making the request
*/
else if(request.getParameter("action").equals("getGroups")) {
/* Query for the User by username */
User user = queryUser(request.getParameter("user"), pm);
/* Generate the list of groups this user belongs to */
ArrayList<Group> groups = null;
if(user != null) {
groups = new ArrayList<Group>(user.groups().size());
for(Group group : user.groups())
groups.add(group);
}
/* Send response back to the client */
response.setContentType("text/plain");
response.getWriter().write("Just a test");
}
A side question, do I send HTTP POST requests to http://myapp.appspot.com/myapplink
or just to http://myapp.appspot.com/?
I have low experience writing client-server code so I was looking for help and examples of a simple POST request using supplied params and then reading the response back (with in my example the test string) and display it to the terminal.
Here is a sample of a test I was running:
public static void main(String[] args) throws IOException {
String urlParameters = "action=getGroups&username=homer.simpson";
String request = "http://myapp.appspot.com/myapplink";
URL url = new URL(request);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setInstanceFollowRedirects(false);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("charset", "utf-8");
connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length));
connection.setUseCaches (false);
DataOutputStream wr = new DataOutputStream(connection.getOutputStream ());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
if ( connection.getResponseCode() == HttpURLConnection.HTTP_OK ){
System.out.println("Posted ok!");
System.out.println("Res" + connection.getResponseMessage()); //OK read
System.out.println("Size: "+connection.getContentLength()); // is 0
System.out.println("RespCode: "+connection.getResponseCode()); // is 200
System.out.println("RespMsg: "+connection.getResponseMessage()); // is 'OK'
}
else {
System.out.println("Bad post...");
}
}
When executing however, I get that it's a "bad post"

Usually you will want to send it to a particular link, so you have a way of separating the different servlet classes. Assuming that the doPost() method is inside MyAppLinkServlet class in the package myapp, you will need a web.xml file like the one below to describe how you will respond to the link. BTW, the code is only slightly modified from the GAE/J example at http://code.google.com/appengine/docs/java/gettingstarted/creating.html
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<servlet>
<servlet-name>myapplink</servlet-name>
<servlet-class>myapp.MyAppLinkServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myapplink</servlet-name>
<url-pattern>/myapplink</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
On the server, try adding the line
response.setStatus(200);
(which effectively sets the status as "OK").
On the client side, try something simple to start, such as:
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class TestRequest {
public static void main(String[] args) throws IOException {
String urlParameters = "action=getGroups&username=homer.simpson";
String request = "http://myapp.appspot.com/myapplink";
URL postUrl = new URL (request+"?"+urlParameters);
System.out.println(readFromUrl(postUrl));
}
private static String readFromUrl (URL url) throws IOException {
FetchOptions opt = FetchOptions.Builder.doNotValidateCertificate(); //depending on how did you install GAE, you might not need this anymore
HTTPRequest request = new HTTPRequest (url, HTTPMethod.POST, opt);
URLFetchService service = URLFetchServiceFactory.getURLFetchService();
HTTPResponse response = service.fetch(request);
if (response.getResponseCode() == HttpURLConnection.HTTP_OK) {
byte[] content = response.getContent();
return new String(content);
} else {
return null;
}
}
}
Good luck!

Related

Java mail API works on localhost bu not on google app engine

I have a sending email logic using Java Mail API and it works fine when I am on localhost but once deployed on google cloud platform, the email goes to my servlet but never gets delivered.
I bought the email from GoDaddy so it's: xxx#mydomain.com.
After reading docs on google cloud platform and some comments here on StackOverflow, I have configured firewall rules but nothing seems to work to allow ingress and egress on port 25, 465 and 587 (I know google doesn't allow traffic on port 25).
I don't wanna use 3rd party email senders like sendbird... because I was using elastic before and I didn't need a 3rd party email sender, JavaMail was enough.
So I think Java mail should be enough for GCP.
Can anyone help me out?
Here is my sending email logic
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
/**
*
* #author sidibe ibrahim
* Sending email logic
*/
public class EmailSender {
static MessagingException me;
public static boolean sendMail(String from, String password, String message, String to[], String title) throws UnsupportedEncodingException {
String host = "smtpout.secureserver.net";
Properties props = System.getProperties();
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", host);
props.put("mail.smtp.user", from);
props.put("mail.smtp.password", password);
props.put("mail.smtp.host", 465);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.ssl.trust", "*");
Session session = Session.getDefaultInstance(props, null);
MimeMessage mimeMessage = new MimeMessage(session);
try {
mimeMessage.setFrom(new InternetAddress(from, "xxx"));
InternetAddress[] toAddress = new InternetAddress[to.length];
for (int i = 0; i < to.length; i++) {
toAddress[i] = new InternetAddress(to[i]);
}
for (int i = 0; i < toAddress.length; i++) {
mimeMessage.addRecipient(Message.RecipientType.TO, toAddress[i]);
}
//sdd subject
mimeMessage.setSubject(title);
//set message to mimeMessage
mimeMessage.setText(message, "UTF-8", "html");
Transport transport = session.getTransport("smtp");
transport.connect(host, from, password);
transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
transport.close();
return true;
} catch (MessagingException m) {
me.printStackTrace();
}
return false;
}
}
The App Engine Mail API (which also supports JavaMail) has already been deprecated.
Instead, GCP recommends to use a third-party mail provider such as:
SendGrid
Mailgun
Mailjet
EDIT
If you would still like to continue with the outdated solution, however, see the article on sending emails with the Mail API.

which are the files uri on GAE java emulating cloud storage with GCS client library?

I'm developing a web application using Google app engine for Java.
I will use Google Cloud storage and according to the documentation, I'm using GCS client library to emulate cloud storage on local disk.
I have no problem saving the files, I can see them from eclipse under the war folder (under the path WEB-INF/appengine-generated) and I can see them from the web admin panel accessible from the url
localhost:8888/_ah/admin
as indicated in this question
My question is the following. Which are the files URI under localhost to access them with GCS emulation?
Example of one of uploaded files on localhost:
file key is aglub19hcHBfaWRyJwsSF19haF9GYWtlQ2xvdWRTdG9yYWdlX18xIgpxcmNvZGUuanBnDA
ID/name is encoded_gs_key:L2dzLzEvcXJjb2RlLmpwZw
filename is /gs/1/qrcode.jpg
Thanks in advance.
You can see how this is done here:
https://code.google.com/p/appengine-gcs-client/source/browse/trunk/java/src/main/java/com/google/appengine/tools/cloudstorage/dev/LocalRawGcsService.java
As of today this mapping is being maintained by the using the local datastore. This may change in the future, but you should be able to simply call into this class or one of the higher level classes provided with the GCS client to get at the data.
Using getServingUrl()
The local gcs file is saved into a blob format.
When saving it, I can use location like your filename "/gs/1/qrcode.jpg"
Yet, when accessing it, this fake location is not working.
I found a way. It may not be the best, but works for me.
BlobKey bk = BlobstoreServiceFactory.getBlobstoreService().createGsBlobKey(location);
String url = ImagesServiceFactory.getImagesService().getServingUrl(bk);
The url will be like:
http://127.0.0.1:8080/_ah/img/encoded_gs_key:yourkey
(I was hardly to find any direct solution by google search.
I hope this answer can help others in need.)
Resource: ImagesServiceFactory ImageService
FileServiceFactory
For those who wish to serve the local GCS files that have been created by the GAE GCS library, one solution is to expose a Java Servlet like this:
package my.applicaion.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
public final class GoogleCloudStorageServlet
extends HttpServlet
{
#Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException
{
final BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
final String fileName = "/gs" + request.getPathInfo();
final BlobKey blobKey = blobstoreService.createGsBlobKey(fileName);
blobstoreService.serve(blobKey, response);
}
}
and in your web.xml:
<servlet>
<servlet-name>GoogleCloudStorage</servlet-name>
<servlet-class>my.applicaion.servlet.GoogleCloudStorageServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GoogleCloudStorage</servlet-name>
<url-pattern>/gcs/*</url-pattern>
</servlet-mapping>
If you host this servlet in your GAE application, the URL for accessing a GCS file with bucket bucket-name and with name fileName is http://localhost:8181:/gcs/bucket-name/fileName, the local GAE development server port number being 8181.
This works at least from GAE v1.9.50.
And if you intend to have the local GCS server working in a unit test with Jetty, here is a work-around, hopefully with the right comments:
final int localGcsPortNumber = 8081;
final Server localGcsServer = new Server(localGcsPortNumber);
final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
final String allPathSpec = "/*";
context.addServlet(new ServletHolder(new HttpServlet()
{
#Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
final BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
final String fileName = "/gs" + request.getRequestURI();
final BlobKey blobKey = blobstoreService.createGsBlobKey(fileName);
if (blobKey != null)
{
// This is a work-around over the "ServeBlobFilter" which does not take the "Content-Type" from the "blobInfo", but attempts to retrieve it from the "blobKey"
final BlobInfo blobInfo = BlobStorageFactory.getBlobInfoStorage().loadGsFileInfo(blobKey);
if (blobInfo != null)
{
final String contentType = blobInfo.getContentType();
if (contentType != null)
{
response.addHeader(HttpHeaders.CONTENT_TYPE, contentType);
}
}
}
blobstoreService.serve(blobKey, response);
}
}), allPathSpec);
// The filter is responsible for taken the "blobKey" from the HTTP header and for fulfilling the response with the corresponding GCS content
context.addFilter(ServeBlobFilter.class, allPathSpec, EnumSet.of(DispatcherType.REQUEST));
// This attribute must be set, otherwise a "NullPointerException" is thrown
context.getServletContext().setAttribute("com.google.appengine.devappserver.ApiProxyLocal", LocalServiceTestHelper.getApiProxyLocal());
localGcsServer.setHandler(context);
localGcsServer.start();

GAE and GWT - response.sendRedirect is not redirect

I am creating an application that needs an image upload function. I have been following this tutorial and the App Engine Documentation.
The image is uploaded correctly, and the server is redirected to the doPost function of the FileUpload HttpServlet. I can read the blob key from the request and save it in the datastore.
My problem is sending a response back to the client. Everything I've seen points to using the response.sendRedirect function, but that has not been successful yet.
public class FileUpload extends HttpServlet
{
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
Map<String, List<BlobKey>> blobs = blobstoreService.getUploads(request);
BlobKey blobKey = blobs.get("picFileUpload").get(0);
ShipHull shipHull = new ShipHull();
shipHull.setShipHullName(request.getParameter("hullNameText"));
shipHull.setShipImageURL("/shipbuilder/blobService?blob-key=" + blobKey.getKeyString());
PersistenceManager pm = PMF.get().getPersistenceManager();
try
{
pm.makePersistent(shipHull);
}
catch (Exception e)
{
String hi = "hello";
}
finally
{
pm.close();
}
Boolean test = response.isCommitted();
response.sendRedirect("/shipbuilder/FileUpload?gwt.codesvr=127.0.0.1:9997&shipHullName=" + shipHull.getShipHullName());
test = response.isCommitted();
return;
}
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
String id = req.getParameter("shipHullName");
resp.setHeader("Content-Type", "text/html");
resp.getWriter().println(id);
}
}
I am trying to redirect the client back to the doGet in this same servlet. I have tried this and without the gwt.codesvr=127.0.0.1:9997 and the shipHullName=" + shipHull.getShipHullName()) but the doGet function is never reached. I have also tried https://www.google.com.
This is all being done on a development server (haven't tried on the production server yet).
If you have any other methods of returning the status of the image save (like if a filename is already taken), I wouldn't mind trying something different.
Thanks!
Can you try just putting a string for success/failure into resp object.
public void doPost( HttpServletRequest req, HttpServletResponse resp ) throws ServletException, IOException
{
try{
processFileUploads(req)
resp.getWriter().print( "Success" );
}{
catch(Exception e){
resp.getWriter().println( "Unable to upload the file - Upload Failed" );
}
}
I have figured out the problem. I guess I was having the same problem as this post.
I had to click on the GWT Development Mode toolbox icon and add the webserver "ammo-box" (Name of my computer" and Code Server as "127.0.0.1". When I directed my browser to that development link, it all worked, even the answer you gave SSR. This was a domain switching problem.
Thanks for the help.

Exporting data to Google Spreadsheet from Google App Engine (Java version)

I am getting no where now. When I Google around to find a sample of "Exporting data to Google Spreadsheet from Google App Engine", I see a lot of Google Conversion API, Google Conversion API, Google Spreadsheet API and Google Docs API tutorials. But then they all deprecated from Google when I check at the Google site??? So, what is the most updated right now so I can make use of it?
=====
Okay now I use the Google Drive SDK via OAuth2 to create text file. But I have problem with this:
this is the error:
java.lang.NullPointerException at
java.net.URI$Parser.parse(URI.java:3004) at
java.net.URI.(URI.java:577) at
com.google.api.client.http.GenericUrl.(GenericUrl.java:100) at
com.google.api.client.googleapis.media.MediaHttpUploader.upload(MediaHttpUploader.java:269)
at
com.google.api.services.drive.Drive$Files$Insert.executeUnparsed(Drive.java:309)
at
com.google.api.services.drive.Drive$Files$Insert.execute(Drive.java:331)
at
com.company.dashboard.service.DriveService.initDoc(DriveService.java:84)
this is the code:
private GoogleCredential buildGoogleCredential(Credential credential) {
try {
logger.warning(oauth2Service.getClientCredential().toString());
GoogleCredential googleCredential = new GoogleCredential.Builder()
.setClientSecrets(oauth2Service.getClientCredential())
.setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory()).build();
googleCredential.setAccessToken(credential.getAccessToken());
googleCredential.setRefreshToken(credential.getRefreshToken());
return googleCredential;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private Drive buildService(GoogleCredential credential) {
return new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential).build();
}
public void initDoc(HttpServletRequest req)
throws Exception {
User user = UserServiceFactory.getUserService().getCurrentUser();
Credential credential = oauth2Service.getStoredCredential(
user.getUserId(),
(CredentialStore)req.getSession().getServletContext().getAttribute(OAuth2Constant.GOOG_CREDENTIAL_STORE));
if (credential != null) {
logger.warning("Using access token: " + credential.getAccessToken());
try {
GoogleCredential googleCredential = buildGoogleCredential(credential);
Drive service = buildService(googleCredential);
if (service == null) {
logger.warning("very bad!");
}
File body = new File();
body.setTitle("My document");
body.setDescription("A test document");
body.setMimeType("text/plain");
java.io.File fileContent = new java.io.File("document.txt");
FileContent mediaContent = new FileContent("text/plain", fileContent);
service.files().insert(body, mediaContent).execute();
//File file = service.files().insert(body, mediaContent).execute();
//System.out.println("File ID: " + file.getId());
} catch (HttpResponseException e) {
if (e.getStatusCode() == 401) {
logger.warning(e.getMessage());
// Credentials have been revoked.
// TODO: Redirect the user to the authorization URL.
throw new UnsupportedOperationException();
}
} catch (IOException e) {
System.out.println("An error occurred: " + e);
}
}
oauth2Service.getClientCredential() returns (xxx = client id and client secret code, not shown in here)
{"web":{"client_id":"xxx.apps.googleusercontent.com","client_secret":"xxx"}}
This is my scope:
final static List<String> SCOPES = Arrays.asList("https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/docs",
"https://www.googleapis.com/auth/drive.file");
final static String AUTH_RESOURCE_LOC = "/client_secrets.json";
final static String OATH_CALLBACK = "http://localhost:8888/oauth2callback";
This line
service.files().insert(body, mediaContent).execute();
throws NullPointerException. Any idea what had gone wrong???
P/S: Credential = com.google.api.client.auth.oauth2.Credential. I have my OAuth2 everything works good. I can retrieve user info without problem but not the Drive API. service is not null as you see I put it the log as "very bad" and it is not shown. No 401 exception is thrown means my Oauth2 is good with the scopes.
=======
DARN!!!! Finally solved the issue!!! My code was totally correct! Just I enabled the wrong API! It should be Drive API instead of Drive SDK API :/
You can generate a csv file from GAE and upload it using the Drive API with ?convert=true to have it automatically converted to a Google spreadsheet:
https://developers.google.com/drive/v2/reference/files/insert

app engine incoming mail handling

I am developing a Google App Engine application.
I wish to receive mails under '%username%#appid.appspotmail.com', where %username% belongs to a user of the application.
I just can't figure out what to define in web.xml file.
Any similar solution such as mails to:
'%username%.usermailbox#appid.appspotmail.com'
'usermailbox.%username%#appid.appspotmail.com'
is acceptable (if it makes it easier with the wildcards).
I've tried (as suggested by Gopi)
mapping the relevant servlet to <url-pattern>/_ah/mail/user.*</url-pattern> within the web.xml file. It's not working.
The client gets a bounce message, whereas the server logs, do show a relevant request received by the app, but rejected with a 404. No "No handlers matched this URL." INFO is added to the log entry. In addition, when GETing the generated URL, I don't get a 'This page does not support GET', but rather a plain 404.
If I however send mail to say 'info#appid.appspotmail.com', the logs show a 404 (which they should, as it's not mapped in the web.xml). In addition, for such a request, a "No handlers matched this URL." INFO is added to the relevant log entry.
Needless to say that, Incoming mail IS found under Configured Services.
This change happened when App Engine started using a true Java web server (and so Toby's explanation is spot on... sadly I can't seem to recover my login to vote it up!). My recommendation is to use a Filter. I played around with the filter below when writing a toy app for GAE. You once you've defined the base class at the end of this post, you can can create a series of mail handlers (like the following). All you have to do is register each filter in your web.xml to handle /_ah/mail/*.
public class HandleDiscussionEmail extends MailHandlerBase {
public HandleDiscussionEmail() { super("discuss-(.*)#(.*)"); }
#Override
protected boolean processMessage(HttpServletRequest req, HttpServletResponse res)
throws ServletException
{
MimeMessage msg = getMessageFromRequest(req);
Matcher match = getMatcherFromRequest(req);
...
}
}
public abstract class MailHandlerBase implements Filter {
private Pattern pattern = null;
protected MailHandlerBase(String pattern) {
if (pattern == null || pattern.trim().length() == 0)
{
throw new IllegalArgumentException("Expected non-empty regular expression");
}
this.pattern = Pattern.compile("/_ah/mail/"+pattern);
}
#Override public void init(FilterConfig config) throws ServletException { }
#Override public void destroy() { }
/**
* Process the message. A message will only be passed to this method
* if the servletPath of the message (typically the recipient for
* appengine) satisfies the pattern passed to the constructor. If
* the implementation returns <code>false</code>, control is passed
* o the next filter in the chain. If the implementation returns
* <code>true</code>, the filter chain is terminated.
*
* The Matcher for the pattern can be retrieved via
* getMatcherFromRequest (e.g. if groups are used in the pattern).
*/
protected abstract boolean processMessage(HttpServletRequest req, HttpServletResponse res) throws ServletException;
#Override
public void doFilter(ServletRequest sreq, ServletResponse sres, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) sreq;
HttpServletResponse res = (HttpServletResponse) sres;
MimeMessage message = getMessageFromRequest(req);
Matcher m = applyPattern(req);
if (m != null && processMessage(req, res)) {
return;
}
chain.doFilter(req, res); // Try the next one
}
private Matcher applyPattern(HttpServletRequest req) {
Matcher m = pattern.matcher(req.getServletPath());
if (!m.matches()) m = null;
req.setAttribute("matcher", m);
return m;
}
protected Matcher getMatcherFromRequest(ServletRequest req) {
return (Matcher) req.getAttribute("matcher");
}
protected MimeMessage getMessageFromRequest(ServletRequest req) throws ServletException {
MimeMessage message = (MimeMessage) req.getAttribute("mimeMessage");
if (message == null) {
try {
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
message = new MimeMessage(session, req.getInputStream());
req.setAttribute("mimeMessage", message);
} catch (MessagingException e) {
throw new ServletException("Error processing inbound message", e);
} catch (IOException e) {
throw new ServletException("Error processing inbound message", e);
}
}
return message;
}
}
the following provides a plausable explanation, thanks to
url-pattern and wildcards
which refers to
http://jcp.org/aboutJava/communityprocess/mrel/jsr154/index2.html (scroll to section 11.2)
In the url-pattern the * wildcard behaves differently to how one would assume,
it is treated as a normal character, except
-when the string ends with /* for "path mapping"
-or it begins with *. for "extension mapping"
Too bad, would have been nice to wildcard-match email recipient addresses to different servlets, as depicted in Google's API doc samples. I'm using absolute matches now which isn't as clean as the appid needs to be included.
I think putting an entry similar to below into your web.xml should work to match your second case 'usermailbox.%username%#appid.appspotmail.com
<servlet>
<servlet-name>handlemail</servlet-name>
<servlet-class>HandleMyMail</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>handlemail</servlet-name>
<url-pattern>/_ah/mail/usermailbox.*</url-pattern>
</servlet-mapping>
Well... After trying every possible solution/url-mapping, I went with fast and ugly one.
The gist is to have a single "catch all" mail servlet, to work as a dispatcher to other, specific, servlets. It's like a giant switch, where the parameter is the request URL.
This is NOT what I wished for, but it works, and seems to be the only one that does.
I have a single servlet IncomingMail that handles ALL incoming mail. period.
So now, the only mapping of URLs under /_ah/mail/ is the following:
<servlet>
<servlet-name>IncomingMail</servlet-name>
<servlet-class>IncomingMail</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>IncomingMail</servlet-name>
<url-pattern>/_ah/mail/*</url-pattern>
</servlet-mapping>
In addition, I have the following servlet, mapped as a "plain-old-servlet":
(notice the <url-pattern>, not a "mail mapped" servlet)
<servlet>
<servlet-name>GetUserMail</servlet-name>
<servlet-class>GetUserMail</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>GetUserMail</servlet-name>
<url-pattern>/serv/userMail</url-pattern>
</servlet-mapping>
The catch-all servlet (would eventually) look like a giant switch:
public class IncomingMail extends HttpServlet {
private final String USER_MAIL_PREFIX="http://appid.appspot.com/_ah/mail/user.";
private final String USER_MAIL_SERVLET="/serv/userMail";
...
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String url = req.getRequestURL().toString();
System.out.println("IncomingMail called, with URL: "+url);
String email;
String servlet;
if (url.startsWith(USER_MAIL_PREFIX)) {
email=url.replace(USER_MAIL_PREFIX, "");
servlet=USER_MAIL_SERVLET;
}//userMail
if (url.startsWith(OTHER_PREFIX)) {
//Redirect to OTHER servlet
}
...
System.out.println("forward to '"+servlet+"', with email '"+email+"'");
RequestDispatcher dispatcher=req.getRequestDispatcher(servlet);
try {
req.setAttribute("email", email);
dispatcher.forward(req, resp);
} catch (ServletException e) {
System.err.println(e);
}
}
}
The destination servlet (GetUserMail in this case), does a getRequestParameter("email"), to see the specific destined mailbox.
It will receive all mails sent to 'user.%un%#appid.appspotmail.com', where %un% is a username in the application space.
The email parameter received by the servlet would be of the form '%un%#appid.appspotmail.com', without the discerning prefix.
Each such "specific" servlet, would get "its cut" from the mail dispatcher servlet, with the email parameter already without the discerning prefix.
One note I will add under security:
If you're worried of bogus requests to the "specific servlets", just define them all under a common virtual namespace say /servmail/ in your site, and define a new <security-constraint> to allow requests to originate only within the application itself.
Like so (inside web.xml):
<security-constraint>
<web-resource-collection>
<web-resource-name>MailServlets</web-resource-name>
<description>policy for specific mail servlets</description>
<url-pattern>/servmail/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
Would still love to hear from someone that tried and succeeded in doing a wildcard <url-pattern> mail mapping, other than a catch-all one.
I had a similar problem (using Python, so yaml config files rather than XML) and the cause turned out to be because I put the:
- url: /_ah/mail/.+
script: handle_incoming_email.py
login: admin
before an existing catch-all entry:
- url: /.*
script: main.py
This gave 404s on the server and "Message send failure" when sending test messages.
Moving it after the catch-all entry solved the problem.
I'm fairly sure the problem is just that you're trying to use .*. URL expressions in web.xml are globs, not regular expressions, so you should use just * instead - .* will only match strings starting with a dot.

Resources