code up

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Jersey(2.0-m12-1)をTomcatで動かす - 第二回

前回に続きJersey 2.0-m12-1の話。

今回はJSONオブジェクトの送受信ができるようにした。

web.xml

web.xml前回から変更なし。

Application

Applicationクラス。パッケージを指定してリソースとプロバイダを自動探査するように変更。1.xの頃にcom.sun.jersey.config.property.packagesというサーブレットのinit-paramで指定していたものの置き換え・・・かも。

package myapp.rest;

import javax.ws.rs.ApplicationPath;

import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("/resources/")
public class MyRESTApplication extends ResourceConfig {

	public MyRESTApplication() {
		// 区切り文字はスペースとか;も可
		packages(false, "myapp.rest.resources,myapp.rest.providers");
	}

}

これによりmyapp.rest.resourcesmyapp.rest.providers以下を自動探査するようになる。尚、recursive[第一引数]をfalseにしているので下位パッケージは探査しない。

Provider

続いてProvider。JSONの入力、出力をするにはJacksonなどがサポートされているようだが、現在はJSONICを使用しているため、独自のMessageBodyWriterMessageBodyReaderを作成。

package myapp.rest.providers;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import net.arnx.jsonic.JSON;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Provider
public class MyRESTProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object>, ExceptionMapper<Throwable> {
	private final static Logger LOG = LoggerFactory.getLogger(MyRESTProvider.class);

	@Override
	public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
		return MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
	}

	@Override
	public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders,
					InputStream entityStream) throws IOException, WebApplicationException {
		return JSON.decode(entityStream, type);
	}

	@Override
	public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
		return MediaType.APPLICATION_JSON_TYPE.isCompatible(mediaType);
	}

	@Override
	public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
		// As of JAX-RS 2.0, the method has been deprecated and
		// the value returned by the method is ignored by a JAX-RS runtime.
		return -1L;
	}

	@Override
	public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders,
					OutputStream entityStream) throws IOException, WebApplicationException {
		JSON.encode(t, entityStream);
	}

	@Override
	public Response toResponse(Throwable t) {
		LOG.warn("REST Exception", t);

		ResponseBuilder builder = null;

		if (t instanceof WebApplicationException) {
			WebApplicationException we = (WebApplicationException) t;

			if (we.getResponse() != null) {
				builder = Response.fromResponse(we.getResponse());
			}
		}

		if (builder == null) {
			builder = Response.status(Status.INTERNAL_SERVER_ERROR);
		}

		return builder.type(MediaType.APPLICATION_JSON_TYPE.withCharset("UTF-8")).entity(new FooJson()).build();
	}

}

例外時、デフォルトのHTML(JSP)のエラー画面となってしまうためExceptionMapperも実装して全ての応答をJSON形式(FooJsonインスタンスをJSON化)にしている。

Resource

続いてリソースクラス。HTMLフォームによる送信(Content-Type: application/x-www-form-urlencoded)と直JSONオブジェクトの送信(Content-Type: application/json)どちらでも受け取れるようにした。

リソースクラスのインスタンスのライフサイクルはJAX-RS 2.0に記載のある通り、

By default a new resource class instance is created for each request to that resource.

デフォルトではリソースクラスのインスタンスはリクエスト毎に作成される。とあるのでインジェクション(@Context)をインスタンスフィールドに定義している。

使うJSONライブラリにもよるだろうが、JSONオブジェクトを送信する際にはURLエンコードをせずに送信している。エンコードするとデータによってはJSONIC側で例外が発生する。

逆に(Tomcatの場合)application/x-www-form-urlencodedで送信されたデータは(送信元でURLエンコードしたものをTomcatが自動的に)URLデコードするので@Encodedアノテーションはつけていない。

package myapp.rest.resources;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Path("/hello")
public class HelloWorldResource extends AbstractService {
	private final static Logger LOG = LoggerFactory.getLogger(HelloWorldResource.class);

	// http://stackoverflow.com/questions/8482539/context-httpservletrequest-scope-in-jersey
	@Context
	HttpServletRequest request;

	@GET
	@Produces(MediaType.TEXT_PLAIN)
	public String getHello() {
		return "Hello World!";
	}

	@POST
	@Path("/post")
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_JSON)
	public FooJson post(FooJson json) {
		if (json == null) {
			return null;
		}

		// 送信されたデータの登録処理など。

		return json;
	}

	@POST
	@Path("/post")
	@Produces(MediaType.APPLICATION_JSON)
	@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
	public FooJson post(@FormParam("id") String id, @FormParam("data") String data) {
		// MediaType.APPLICATION_FORM_URLENCODED_TYPE.withCharset("UTF-8").equals(MediaType.APPLICATION_FORM_URLENCODED)
		// がtrueにならないので、; charset=UTF-8があると、FormParamが飛んでこない。

		FooJson json = new FooJson();

		json.setId(userId);
		json.setData(Base64.decodeBase64(data));

		return post(json);
	}

}

クライアント側から送信されるHTTPヘッダー(Content-Type)の問題

今回はjQueryを使って両方のメソッド(application/x-www-form-urlencoded, application/json)を試したのだが、Content-Type: application/x-www-form-urlencodedで送ると@FormParamに値がセットされるが、Content-Type: application/x-www-form-urlencoded; charset=UTF-8で送ると@FormParamの値がセットされない(常にnull)という事象があった。MultivaluedMap<String, String>では値が受け取れたので、バグっぽいんだけどJerseyのドキュメントやJSR-339の仕様書を読んでも原因がよく分からず。

最後に、送信するそれぞれのリクエストイメージ。※色々省略。dataフィールドにはBASE64値をセットしているがそれぞれでURLエンコードの有無が異なっている。

HTMLフォーム風リクエスト(application/x-www-form-urlencoded)

POST /myapp/resources/hello/post HTTP/1.0
Accept: application/json
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 31
User-Agent: (略)

id=12345678&data=MTIzNDU2Nzg%3D

JSONオブジェクトリクエスト(application/json)

POST /myapp/resources/hello/post HTTP/1.0
Accept: application/json
Host: localhost:8080
Content-Type: application/json
Content-Length: 39
User-Agent: (略)

{"id":"12345678","data":"MTIzNDU2Nzg="}
関連記事
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。