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.
Related
I am using spring 4 + hibernate 4 + spring security (RESTFull Webservice API) and angular at front. (Very new to this all).
My failure handler is as follows:
#Component
public class AuthFailure extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
If my request fails I get 401 response which is as expected. Now I want to add AccountLocked and CredentialsExpiredException functionality. I am setting "false" appropriately when I return "User". Again I do get response with status as 401.
return new User(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, AuthorityUtils.NO_AUTHORITIES);
As I always get 401 and I do not get any user object inside response(Or may be don't know how to get it), at front end I am unable to find whether it's due to bad credentials or account locked or credentials expired as I want to redirect to another page. I also tried to catch exceptions and tried to forward different statuses but does not seems to hit this code. I always get 401.
#ExceptionHandler(LockedException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public ModelAndView handleLockedException(Exception e) {
logger.error("Exception occurred => " + e.getMessage());
return new ErrorResponse(HttpStatus.BAD_REQUEST.value(), e.getMessage(), "Spring security exception").asModelAndView();
}
Please help me - how shall I handle at angular end so that I can redirect to appropriate page?
I think solution was very simple, just did not pay attention to AuthFailure method parameters. Modified my AuthFailuar as follows:
#Component
public class AuthFailure extends SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
if (exception instanceof LockedException) {
response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
}else if {
....
} else {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
Now as I have given different status codes I can easily differentiate at the front. Hope for somebody this would be helpful.
Im trying to implement google's Cloud Connection Server with Google App Engine following this tutorial -
Implementing an XMPP-based App Server. I copied latest smack jars from http://www.igniterealtime.org/projects/smack/ (smack.jar and smackx.jar), put them in WEB-INF/lib and added them to the classpath (im using eclipse).
In the code sample in the first link i posted, the XMPPConnection is initiated in a 'main' method. Since this is not really suitable to GAE i created a ServletContextListener and added it to web.xml.
public class GCMContextListener implements ServletContextListener {
private static final String GCM_SENDER_ID = "*GCM_SENDER_ID*";
private static final String API_KEY = "*API_KEY*";
private SmackCcsClient ccsClient;
public GCMContextListener() {
}
#Override
public void contextInitialized(ServletContextEvent arg0) {
final String userName = GCM_SENDER_ID + "#gcm.googleapis.com";
final String password = API_KEY;
ccsClient = new SmackCcsClient();
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
}
#Override
public void contextDestroyed(ServletContextEvent arg0) {
try {
ccsClient.disconnect();
} catch (XMPPException e) {
e.printStackTrace();
}
}
}
web.xml
<web-app>
<listener>
<listener-class>com.myserver.bootstrap.GCMContextListener</listener-class>
</listener>
</web-app>
Now, when i start the GAE server i get the following exception :
java.lang.NoClassDefFoundError: javax.naming.directory.InitialDirContext is a restricted class. Please see the Google App Engine developer's guide for more details.
i searched the "Google App Engine developer's guide for more details" but couldnt find anything about this. can you please help me ?
Google App Engine restricts access to certain JRE classes. In fact they published a whitelist that shows you which classes are useable. It seems to me that the Smack library might require some reference to a directory context (maybe to create the XMPP messages?) and that is why your servlet causes this exception. The javax.naming.directory is not in the whitelist.
I'm currently working on setting up a GCM Server as well. It seems to me that you need to read through the example and see what that main method is doing. What I see is a connection to the GCM server:
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
Then a downstream message being sent to a device:
// Send a sample hello downstream message to a device.
String toRegId = "RegistrationIdOfTheTargetDevice";
String messageId = ccsClient.getRandomMessageId();
Map<String, String> payload = new HashMap<String, String>();
payload.put("Hello", "World");
payload.put("CCS", "Dummy Message");
payload.put("EmbeddedMessageId", messageId);
String collapseKey = "sample";
Long timeToLive = 10000L;
Boolean delayWhileIdle = true;
ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey,
timeToLive, delayWhileIdle));
}
These operations would be completed at some point during your application's lifecycle, so your servlet should support them by providing the methods the example is implementing, such as the connect method that appears in the first piece of code that I pasted here. It's implementation is in the example at line 235 if I'm not mistaken.
As the documentation says, the 3rd party application server, which is what you're trying to implement using GAE, should be:
Able to communicate with your client.
Able to fire off properly formatted requests to the GCM server.
Able to handle requests and resend them as needed, using exponential back-off.
Able to store the API key and client registration IDs. The API key is included in the header of POST requests that send messages.
Able to store the API key and client registration IDs.
Able to generate message IDs to uniquely identify each message it sends.
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.
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!
our project runs on GWT and Java App Engine and we use the standard GWT RPC mechanism.
App engine adds log trace for each RPC call, but it just logs the servlet URL and not the called method.
We would like to add the method name to the log URL.
We have tried extending RpcRequestBuilder class, overriding doCreate and adding the method name to the URL, but the problem is that at this point the method name is unknown - it's known later at doSetRequestData (as part of the data string).
Thanks
Itzik
In each rpc implementation you can override one of readContent and processCall and add logging.
#Override
public String processCall(String payload) throws SerializationException {
// TODO Auto-generated method stub
String processCall = super.processCall(payload);
Logger.getLogger("").info(processCall);
return processCall;
}
#Override
protected String readContent(HttpServletRequest request)
throws ServletException, IOException {
// TODO Auto-generated method stub
String readContent = super.readContent(request);
Logger.getLogger("").info(readContent);
return readContent;
}
Log Line
6|0|4|http://127.0.0.1:8888/_4021625/|35C4974968FC8F8A9A7EA4881FD49F57|com.bitdual.client.LogMeService|logmemethod|1|2|3|4|0|