HttpClientのREST通信サンプル

ラジコン操作で作成したWEBサービスをバックエンドJAVAで連携する自作のサンプル。
操作にはBackbone.jsなどで直接REST実装してもよかったが、今後アームの取り付けなど拡張するためJAVAでAPI化した。
(GIT : https://github.com/tsunaki00/radiocontrol)
【イメージ】
![rest.png](https://qiita-image-store.s3.amazonaws.com/0/52867/9839fbb7-4137-99b0-d787-afb09277b6fd.png)


## 使用したライブラリ
```
・httpcore-4.2.2.jar
・httpclient-4.2.3.jar
・jsonic-1.2.11.jar
・s2-tiger-2.4.44.jar（汎用Beanで利用しているので、commons-beanutilsなどでもOK)
```

##階層
```
 org.tsunaruts.radiocontrolfw
     └─ webservice
             ├ factory
             │  ├─ impl
             │  │    └─DefaultParserFactory.java
             │  └─ AbstractParserFactory.java
             ├ methods
             │    ├─ AbstractWebMethod.java
             │    ├─ GET.java
             │    └─ POST.java
             ├ parser
             │  ├─ impl
             │  │    └─JsonParser.java
             │  └─ AbstractParser.java
             └─ WebService.java
 ```

まず外部から呼び出し用のクラス
【WebService.java】

``` java
package org.tsunaruts.radiocontrolfw.webservice;

import java.io.IOException;
import java.text.ParseException;
import org.tsunaruts.radiocontrolfw.webservice.methods.GET;
import org.tsunaruts.radiocontrolfw.webservice.methods.POST;

/**
 * <pre>
 * WEBサービス用のメソッドを発行するクラス
 * ※※DIコンテナなどでURLを外部から設定する
 * </pre>
 */
public class WebService {
    /** URL */
    public String url;
    /**
     * 
     * @param url
     */
    public void initialize(String url) {
        this.url = url;
    }

    /**
     * WEBサービスを実行します 
     *  ＜GET メソッド＞
     * @return 実行結果EntityList
     */
    public GET get(String path) {
        return new GET(url, path);
    }
    /**
     * WEBサービスを実行します 
     *  ＜POST メソッド＞
     * @return 実行結果Entity
     * @throws IOException 
     * @throws ParseException 
     */
    public POST post(String path) {
        return new POST(url, path);
    }
}
```

次にWEBサービスに対応したメソッド
【AbstractWebMethod.java】

```java
package org.tsunaruts.radiocontrolfw.webservice.methods;

import java.io.IOException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.seasar.framework.beans.util.BeanMap;
import org.seasar.framework.exception.IORuntimeException;
import org.seasar.framework.exception.ParseRuntimeException;
import org.tsunaruts.radiocontrolfw.webservice.factory.AbstractParserFactory;
import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;

/**
 * <pre>
 * WEBメソッドの抽象クラス
 * </pre>
 */
public abstract class AbstractWebMethod {
    /** WEBサービス提供ホスト名   http://localhost */
    protected String host;
    /** WEBサービス path   /action */
    protected String path;
    
    /**
     * コンストラクタ
     * @param host hostを設定します Examples : http://localhost
     * @param path pathを設定      Examples : /action
     */
    public AbstractWebMethod(String host, String path) {
        this.host = host;
        this.path = path;
    }

    /**
     * 子クラスでHttpUriRequestを生成
     * @param parameter リクエストパラメータ
     * @return HttpUriRequest
     * @throws URISyntaxException URLの組み立て失敗
     */
    protected abstract HttpUriRequest getHttpUriRequest(BeanMap parameter) throws URISyntaxException;
    /**
     * @see getStringBodyResult
     */
    public String getStringBodyResult(){
        return getStringBodyResult(null);
    }
    /**
     * RESTの戻り値を返します。
     * @param parameter リクエストパラメータ
     * @return RESTレスポンス
     */
    public String getStringBodyResult(BeanMap parameter) {
        try {
            HttpUriRequest request = getHttpUriRequest(parameter);
            List<String> list = execute(request, String.class, false);
            if (list != null && !list.isEmpty()) {
                return list.get(0);
            }
            return null;
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        } catch (ParseException e) {
            throw new ParseRuntimeException(e);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }
    public <T> T getSingleResult(Class<T> returnType) {
        return getSingleResult(returnType, null);
    }
    /**
     * RESTの戻り値を返します。
     * @param returnType 戻り値の型
     * @param parameter リクエストパラメータ
     * @return RESTレスポンス
     */
    public <T> T getSingleResult(Class<T> returnType, BeanMap parameter) {
        try {
            HttpUriRequest request = getHttpUriRequest(parameter);
            List<T> list = execute(request, returnType, false);
            if (list != null && !list.isEmpty()) {
                return list.get(0);
            }
            return null;
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        } catch (ParseException e) {
            throw new ParseRuntimeException(e);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }
    /**
     * 
     * @return
     */
    public <T> List<T> getResultList(Class<T> returnType) {
        return getResultList(returnType, null);
    }
    /**
     * RESTの戻り値をList返します。
     * @param returnType 戻り値の型
     * @param parameter リクエストパラメータ
     * @return List&lt;RESTレスポンス&gt;
     */
    public <T> List<T> getResultList(Class<T> returnType, BeanMap parameter) {
        try {
            HttpUriRequest request = getHttpUriRequest(parameter);
            List<T> list = execute(request, returnType, true);
            if (list != null && !list.isEmpty()) {
                return list;
            }
            return null;
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        } catch (ParseException e) {
            throw new ParseRuntimeException(e);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }
    /**
     * 外部WEBサービスにリクエストを発行
     * @param request リクエストオブジェクト
     * @param returnType 戻り値型
     * @param isList 戻り値の種類
     * @return RESTレスポンス
     * @throws ParseException レスポンスパースエラー
     * @throws IOException リクエスト処理時の例外
     */
    @SuppressWarnings("unchecked")
    private <T> List<T> execute(HttpUriRequest request, Class<T> returnType, boolean isList) throws ParseException, IOException {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        HttpResponse response = httpClient.execute(request);
        HttpEntity httpEntity = response.getEntity();
        String body = EntityUtils.toString(httpEntity, "UTF-8");
        AbstractParser parser = AbstractParserFactory.getParserFactory().getParser(httpEntity.getContentType().getValue());
        if (returnType == String.class) {
            List<T> ret = new ArrayList<>();
            ret.add((T) body);
            return ret;
        }
        if (parser == null) {
            return null;
        }
        if (isList) {
            return parser.parseList(body, returnType);
        } else {
            List<T> ret = new ArrayList<>();
            ret.add(parser.parse(body, returnType));
            return ret;
        }
    }
    /**
     * @see AbstractWebMethod#uriBuild(BeanMap)
     * @return URIBuilder
     */
    protected URIBuilder uriBuild() {
        return uriBuild(null);
    }
    /**
     * URL組み立て
     * @param parameter リクエストパラメータ(GETのみ)
     * @return URIBuilder
     */
    protected URIBuilder uriBuild(BeanMap parameter) {
        URIBuilder builder = new URIBuilder();
        String scheme = "http://";
        if (host.indexOf("://") > -1) {
            scheme = host.substring(0, host.indexOf("://"));
            host   = host.substring(host.indexOf("://") + "://".length());
        }
        builder.setScheme(scheme);
        builder.setHost(host);
        builder.setPath(path);
        if (parameter != null) {
            Iterator<Map.Entry<String, Object>> ite = parameter.entrySet().iterator();
            while(ite.hasNext()) {
                Map.Entry<String, Object> entry = ite.next();
                builder.setParameter(entry.getKey(), (String) entry.getValue());
            }
        }
        return builder;
    }
}

```

各リクエストメソッド（とりあえずGETとPOST)
【GET.java】

``` java
package org.tsunaruts.radiocontrolfw.webservice.methods;

import java.net.URISyntaxException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.seasar.framework.beans.util.BeanMap;

/**
 * <pre>
 *  GETリクエストを実装する
 * </pre>
 */
public class GET extends AbstractWebMethod {

    public GET(String host, String path) {
        super(host, path);
    }
    
    /* (non-Javadoc) */
    @Override
    protected HttpUriRequest getHttpUriRequest(BeanMap parameter)
                                               throws URISyntaxException {
        return new HttpGet(uriBuild(parameter).build().toString());
    }
}

```

【POST.java】

```java
package org.tsunaruts.radiocontrolfw.webservice.methods;

import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.seasar.framework.beans.util.BeanMap;

/**
 * <pre>
 *  POSTリクエストを実装する
 * </pre>
 */
public class POST  extends AbstractWebMethod {

    public POST(String host, String path) {
        super(host, path);
    }
    
    /* (non-Javadoc) */
    @Override
    protected HttpUriRequest getHttpUriRequest(BeanMap parameter)
                                               throws URISyntaxException {
        try {
            URIBuilder builder = uriBuild();
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            if (parameter != null) {
                Iterator<Map.Entry<String, Object>> ite = parameter.entrySet().iterator();
                while(ite.hasNext()) {
                    Map.Entry<String, Object> entry = ite.next();
                    params.add(new BasicNameValuePair(entry.getKey(), (String) entry.getValue()));
                }
            }
            HttpPost post = new HttpPost(builder.build().toString());
            post.setEntity(new UrlEncodedFormEntity(params));
            return post;
        } catch (UnsupportedEncodingException e) {
            throw new URISyntaxException(parameter.toString(), host + path);
        }
    }
}
```

最後にレスポンス解析周りのクラス
SOAP対応を考えて、AbstractFactoryで実装

【AbstractParserFactory.java】

```java
package org.tsunaruts.radiocontrolfw.webservice.factory;

import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;
import org.tsunaruts.radiocontrolfw.webservice.parser.impl.JsonParser;

/**
 * <pre>
 * contentTypeをみてParserを生成するFactoryクラス
 * </pre>
 */
public abstract class AbstractParserFactory {

    /**
     * 環境に合わせてFactoryクラスを生成する
     * @return AbstractParserFactory
     */
    public static AbstractParserFactory getParserFactory() {
        return new DefaultParserFactory();
    }
    
    /**
     * contentTypeをみてParserを生成します
     * @param contentType contentType
     * @return
     */
    public abstract AbstractParser getParser(String contentType);
}

```

```java
package org.tsunaruts.radiocontrolfw.webservice.factory.impl;

import org.tsunaruts.radiocontrolfw.webservice.factory.AbstractParserFactory;
import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;
import org.tsunaruts.radiocontrolfw.webservice.parser.impl.JsonParser;

/**
 * <pre>
 *  デフォルトのFactoryクラス
 * </pre>
 *
 */
public class DefaultParserFactory extends AbstractParserFactory {

    /* (non-Javadoc) */
    @Override
    public AbstractParser getParser(String contentType) {
        // JSONの場合
        if (contentType != null && contentType.indexOf("application/json") > -1) {
            return new JsonParser();
        }
        // SOAP, HTML, TEXTの場合
        return null;
    }

}
```

【AbstractParser.java】

```java
package org.tsunaruts.radiocontrolfw.webservice.parser;

import java.util.List;

/**
 * <pre>
 * レスポンスを解析クラス
 * </pre>
 */
public interface AbstractParser {
    /**
     * レスポンスを解析します
     * @param body レスポンスボディ
     * @param clazz 戻り
     * @return 取得結果
     */
    <T> T parse(String body, Class<T> clazz);
    /**
     * レスポンスを解析します
     * @param body レスポンスボディ
     * @param clazz 戻り
     * @return 取得結果
     */
    <T> List<T> parseList(String body, Class<T> clazz);
```
【JsonParser.java】

```java
package org.tsunaruts.radiocontrolfw.webservice.parser.impl;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import net.arnx.jsonic.JSON;
import org.seasar.framework.beans.util.Beans;
import org.tsunaruts.radiocontrolfw.webservice.parser.AbstractParser;

/**
 * <pre>
 * JSONを解析するクラス
 * </pre>
 */
public class JsonParser implements AbstractParser {
    /* (non-Javadoc) */
    @Override
    public <T> T parse(String body, Class<T> clazz) {
        return JSON.decode(body, clazz);
    }

    /* (non-Javadoc) */
    @Override
    public <T> List<T> parseList(String body, Class<T> clazz) {
        List<T> ret = new ArrayList<>();
        @SuppressWarnings("unchecked")
        LinkedHashMap<String, Object>[] mapList = JSON.decode(body, LinkedHashMap[].class);
        if (mapList != null) {
            for (LinkedHashMap<String, Object> map : mapList) {
                ret.add(Beans.createAndCopy(clazz, map).execute());
            }
        }
        return ret;
    }
}
```


これで完了、リクエストパラメータをそのままJSONで返すACTIONを作成して単体テスト

## 実装のテスト

```java
package org.tsunaruts.radiocontrolfw.webservice;

import java.util.List;
import org.seasar.framework.beans.util.BeanMap;

public class WebServiceTest {

    public static void main(String[] args) {
        WebService webService = new WebService();
        webService.initialize("http://localhost:8080/radiocontrol");
        BeanMap map = new BeanMap();
        map.put("param1", "test1");
        map.put("param2", "test2");
        List<BeanMap> list = webService.get("/api/control/front")
                                  .getResultList(BeanMap.class, map);
        System.out.println(list);
    }
}
```

```
[{param1=test1}, {param2=test2}]

```
無事に動いた。

【追記】
Stringでレスポンスを返せる修正とFactory周りがおかしかったので反映しました。