Monthly Archives: November 2015

Web Application: Don’t send all the stack trace of an exception to client if Apache Httpd is used as reverse proxy

Don’t send all the stack trace of an exception to client if Apache Httpd is used as reverse proxy.  Otherwise, it may return Http 502 to your client.  

It may say the following just because the response from your servlet container(tomcat etc.) is too big in size: 

502 Bad Gateway 
The proxy server received an invalid response from an upstream server. 

Where to put the environment-specific properties files when using spring framework ?

Where to put the environment-specific properties files when using spring framework ? 

1. Put it on classpath?  Not acceptable.  Classpath means the file will be put in git/svn repository and there will only be one newest copy of it. As a result, all environments will have a same property file. 

2. Put it on an absolute file path?  Will still fail because the difference of OS between different developers developing the same system. 

 a. Let it be "c:/…/system.properties" ? It will work well on Windows, but on Linux or Mac OS it is only a relative path, which is very uncertain depending on how your servlet container defines "current folder". 

 b. Let it be "/home/xxx/…/system.properties" ?  It will work on Windows, which is actually  "c:/home/xxx/…" or "d:/home/xxx/….".  It will surely work on Linux.  However it will fail on Mac OS, because "/home/xxx/…" directories are not supported by Mac OS.  There are ways to make Mac OS support them, but very troublesome. 

My solution is to put them as "${user.home}/…/system.properties" . All operating systems have definite user home directory.  

The problem is Spring’s PropertyPlaceholderConfigurer can’t allow you to use "${user.home}/…/sytem.properties" as the file location because it won’t undertand "${user.home}" in spring xml file. 

To work it around, define your own place holder configurer. 

 
package com.chenjianjx;

import java.io.File;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

/**
 * use this class to locate property files under ${user.home} or its sub
 * directories. In spring context xml, please set "userHomeFiles"  property instead of
 * "locations"
 * 
 *
 */

public class UserHomeFilePropertyPlaceholderConfigurer extends
		PropertyPlaceholderConfigurer {

	private File userHome = new File(System.getProperty("user.home"));

	public void setUserHomeFiles(String[] userHomeFiles) {

		if (userHomeFiles == null || userHomeFiles.length == 0) {
			return;
		}

		Resource[] resources = new Resource[userHomeFiles.length];

		for (int i = 0; i < userHomeFiles.length; i++) {

			String usf = StringUtils.trimToNull(userHomeFiles[i]);
			if (usf == null) {
				continue;
			}

			if (!usf.startsWith("/")) {
				usf = "/" + usf;
			}

			File file = new File(userHome, usf);

			if (!file.exists()) {
				throw new IllegalStateException(
						"The spring property file doesn't exist! File is "
								+ file.getAbsolutePath());
			}

			resources[i] = new FileSystemResource(file);
		}

		this.setLocations(resources);
	}
}



	
		
		
			
				/relative-path-to-user-home/system.properties
				
			
		
	

Jax-RS: Send an empty JSON for void methods

A restful resource’s method can be void. In this case, what should be sent to the client side in terms of http response body ?

According to my experience, you must send an emtpy JSON or an empty XML depending on the content type, instead of sending nothing. In the latter case, some clients may mistakenly consider it as invalid format.

Do this:

return Response.ok("{}").build(); 

Add an SPF DNS record if you don’t want emails sent by your system defined as spam

The problem is, 

  1. Your web application’s domain name is www.foo.com

  2. Your server IP is 123.123.123.123, and it sends emails to users from  support@foo.com 

  3. When a user  danni@gmail.com gets an email from it,  GMAIL may consider it is spam because it doesn’t know whether 123.123.123.123 really represents support@foo.com . 

 What you need to do is to setup a SPF DNS record for your domain, to tell people that 123.123.123.123 can send emails from xxx@foo.com. On Godaddy, you can do this according to, 

https://ph.godaddy.com/help/add-an-spf-record-19218

Email sending using others’ smtp server should done asynchronously

SMTP access provided by 3rd-party sponsors such as Godaddy can be very unreliable. It may take seconds to setup a TCP connection or read and write data. 

As a result, the thread of sending an email may be blocked.  In return, if this thread is a user thread, i.e., created to serve a user’s request, the user can see unacceptable response latency.

You need to do the email sending work in a async way.  Set up a thread pool only for this dirty work.

In addition, you may also want to set up timeout for SMTP connections. Rather fail some emails than have your thread pool run out if all threads are blocked due to poor SMTP connections. 

To setup timeout, you can,

	

 
		
		
		
			
				...
				6000
				6000
			
		
....


How to let MyBatis annotation to set the id and timestamp properties of the JavaBean I passed in ?

If you are with a auto-generated key, when you carry out a method like this

	public void saveNewUser(User user);

what do you expect besides a new record in DB?  

You want the user.id also has been set after the execution. That’s what real ORM does

With MyBatis, you can do it like the following. I believe you already know it.


	@Insert("insert into User(UserName, Password) values (#{userName}, #{password}")
	@SelectKey(statement = "select last_insert_id() as id", keyProperty = "id", keyColumn = "Id", before = false, resultType = Long.class)
	public void saveNewUser(User user);

However, the real world is more complicated. In addition to id propety, it also has createTime and updateTime that should be set after execution. What to do? The solution is a little bit dirty.


	@Insert("insert into User(UserName, Password, CreateTime, UpdateTime) values (#{userName}, now(), now())")
	@SelectKey(statement = "select Id,CreateTime,UpdateTime from User where id =  last_insert_id()", keyProperty = "id,createTime,updateTime" , 
keyColumn = "Id,CreateTime,UpdateTime", 
before = false, resultType = java.util.Map.class)
	public void saveNewUser(User user);

Java Time Zone: It’s 9 o’clock here, what’s your time ?

	private static void printTimeMain() throws ParseException {
		// the computer which runs this program is of the time zone of China
		long someSummerMilis = DateUtils.parseDate("2015-7-4 16:00:00",
				new String[] { "yyyy-MM-dd HH:mm:ss" }).getTime();
		long someWinderMilis = DateUtils.parseDate("2015-11-11 16:00:00",
				new String[] { "yyyy-MM-dd HH:mm:ss" }).getTime();

		System.out
				.println("==============when it is 2015-7-4 16:00:00  in China, the world's times are==============");

		printTime(someSummerMilis, "GMT+8");
		printTime(someSummerMilis, "America/Los_Angeles");
		printTime(someSummerMilis, "GMT-8");
		printTime(someSummerMilis, "Asia/Shanghai");

		System.out
				.println("==============When it is 2015-11-11 16:00:00  in China, the world's times are==============");

		printTime(someSummerMilis, "GMT+8");
		printTime(someSummerMilis, "GMT-8");
		printTime(someWinderMilis, "America/Los_Angeles");
		printTime(someWinderMilis, "Asia/Shanghai");
	}

	private static void printTime(long milis, String timeZoneId) {
		GregorianCalendar calendar = new GregorianCalendar(
				TimeZone.getTimeZone(timeZoneId));
		calendar.setTimeInMillis(milis);

		String s = calendar.getTimeZone().getDisplayName() + "\t"
				+ calendar.get(Calendar.YEAR) + "-"
				+ (calendar.get(Calendar.MONTH) + 1) + "-"
				+ calendar.get(Calendar.DAY_OF_MONTH) + " "
				+ calendar.get(Calendar.HOUR_OF_DAY) + ":"
				+ calendar.get(Calendar.MINUTE) + ":"
				+ calendar.get(Calendar.SECOND) + "\t Day Light Saving Hours ="
				+ (double) calendar.getTimeZone().getDSTSavings() / 3600000;
		System.out.println(s);
	}

 output: 

==============when it is 2015-7-4 16:00:00  in China, the world's times are==============
GMT+08:00	            2015-7-4 16:0:0	 Day Light Saving Hours =0.0
Pacific Standard Time	2015-7-4 1:0:0	 Day Light Saving Hours =1.0
GMT-08:00	            2015-7-4 0:0:0	 Day Light Saving Hours =0.0
China Standard Time	2015-7-4 16:0:0	 Day Light Saving Hours =0.0

==============When it is 2015-11-11 16:00:00  in China, the world's times are==============
GMT+08:00	            2015-7-4 16:0:0	     Day Light Saving Hours =0.0
GMT-08:00	            2015-7-4 0:0:0	     Day Light Saving Hours =0.0
Pacific Standard Time	2015-11-11 0:0:0	 Day Light Saving Hours =1.0
China Standard Time	    2015-11-11 16:0:0	 Day Light Saving Hours =0.0


The best way to deal with CORS issues with Swagger UI

You can set CORS filter on your web.xml or on your tomcat’s web.xml, like this.

     
       CorsFilter
       org.apache.catalina.filters.CorsFilter
     
     
       CorsFilter
       /*
     
  

 However,

1. Your system gets insecure because of this, especially on PROD site where you don’t want someone to invoke your RESTFul services with swagger ui. 

2. There may still be CORS javascript bugs in Swagger UI program even you have set up CORS filter, ince http://petstore.swagger.io/ doesn’t have the same origin with your RESTFul services. 

The best practice is to :

Don’t setup CORS but set up swagger ui static site with the same origin with your RESTFul services, on your local machine, test server,  but not on PROD server.  

1.  The PROD will be safe.

2.  Both the domain name and port should be the same with your RESTFul services.  

3.  You can easily set this up with apache httpd.  

How to signify the end of a self-defined message in TCP programming?

TPC’s data transfer is based on stream. If the two sides don’t agree on how to detect the end of self-defined message, the receiver won’t know the boundary of a message. 

A simple way is to have a special character is the ending flag, such as "2 new lines".

The problem is that the message body can contain ending flags. 

A common-used approach is to specify the byte length of your message. You need to define a header and a body, in the header you tell the length of all the message. It is complicated for message receivers, though. 

Enable https for your apache-httpd-hosted website with a self-signed certificate

Generate a self-signed https certificate

Java’s KeyTool is kind of heavy due to its “keystore” concept. I prefer openssl:

openssl req -x509 -nodes -newkey rsa:2048 -keyout cjx_private.key -out cjx_cert.pem -days 36500
#"-nodes" means no password to access the certificate file
# You will be prompted set up your information. This one is important:
Common Name (e.g. server FQDN or YOUR name) []:*.foo.com   ## Let it be availabe to all sub domains under foo.com 

#After generation you can have a check: 
openssl x509 -in cjx_cert.pem -text 

Now you’ve got two files:
1. cjx_private.key — the private key file
2. cjx_cert.pem — the certificate file

Install it on Apache

Install mod_ssl first

yum install mod_ssl

edit httpd.conf

NameVirtualHost *:443 
...

    ....
    SSLEngine on
    SSLCertificateFile  /somepath/cjx_cert.pem
    SSLCertificateKeyFile /somepath/cjx_private.key