昨日のappengine-java-nightに参加した皆さんなら、エントリのタイトルだけ見たら中身を見る必要はありませんね!
ちょっと今は時間がないのでコードだけうpしますが、クライアント側の環境で通常通りappengineのサービスにアクセスしたら、なぜかデプロイ環境側のサービスにアクセスする、という仕組みが動作しました。
- リクエストされたバイト配列とサービス名、メソッド名、アプリケーション名(うっかり間違ったアプリを触るのを防ぐため。)を使ってmeksynccallをするだけのサーブレットを作成し、デプロイ環境にデプロイする
- クライアント側では、makeSyncCallへのリクエストを上記のサーブレットへ転送するだけのApiProxy.Delegateを実装し、ApiProxy#setDelegate(ApiProxyLocalImpl)した後で、ApiProxy#setDelegate()する。
- クライアント側で普通にデータストアサービスやMemcacheサービスへアクセスする。
- それら全てのサービス(UserServiceはそもそもmakeSyncCallを通らないので無理ですが)へのアクセスがサーバ側で実行される!
サンプル
こんなカンジのサンプルで、デプロイ環境側のデータストアに書き込んだり読み込んだり、memcacheのstatisticsを取得したりする事に成功しました。
@Before public void setUp() throws MalformedURLException { ApiProxy.setEnvironmentForCurrentThread(newEnvironment()); ApiProxy.setDelegate(new ApiProxyLocalImpl(new File("target")) {}); // MakeSyncCallServletへアクセスするためのDelegateで上書き! ApiProxy.setDelegate(new MakeSyncCallServletDelegate(new URL( "${アプリのURL}${MakeSyncCallServletのパス}"))); } @After public void tearDown() { ApiProxyLocalImpl proxy = (ApiProxyLocalImpl) ((MakeSyncCallServletDelegate) ApiProxy .getDelegate()).getOriginal(); ApiProxy.setDelegate(null); ApiProxy.setEnvironmentForCurrentThread(null); } @Test public void runQuery() { Query query = new Query("_makeSyncCallTest"); DatastoreService service = DatastoreServiceFactory.getDatastoreService(); List<Entity> list = service.prepare(query).asList(FetchOptions.Builder.withOffset(0).limit(1000)); for (Entity entity : list) { System.out.println(ToStringBuilder.reflectionToString(entity)); } } @Test public void put() { Entity entity = new Entity("_makeSyncCallTest"); entity.setProperty("timestamp", new Date(System.currentTimeMillis())); DatastoreService service = DatastoreServiceFactory.getDatastoreService(); service.put(entity); } @Test public void statistics() { MemcacheService service = MemcacheServiceFactory.getMemcacheService(); Stats statistics = service.getStatistics(); System.out.println(statistics); }
MakeSyncCallServlet
package com.shin1ogawa.servlet; import java.io.IOException; import java.net.HttpURLConnection; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.wicket.util.io.IOUtils; import com.google.apphosting.api.ApiProxy; /** * リクエストされたbyte配列を{@link ApiProxy#makeSyncCall(String, String, byte[])}へ引き渡し、 * その結果をレスポンスするだけのServlet. * <p>特殊なサーブレットなので、web.xmlにてセキュリティを設定しておくのがおすすめ。</p> * <div><ul> * <li>HttpHeaderに以下を設定する。 * <ul><li>{@literal serviceName}</li><li>{@literal methodName}</li> * <li>{@litera applicationId}</li></ul></li> * <li>payloadにProtocolBufferで出力されたbyte配列を設定して{@literal POST}する。</li> * <li>{@literal application/octet-stream}で * {@link ApiProxy#makeSyncCall(String, String, byte[])}の結果を返すので、クライアント側でよしなに。</li> * </ul></div> * * @author shin1ogawa */ public class MakeSyncCallServlet extends HttpServlet { private static final long serialVersionUID = 2380791176214953417L; private static final String APPLICATION_ID = "applicationId"; private static final String METHOD_NAME = "methodName"; private static final String SERVICE_NAME = "serviceName"; private static Logger logger = Logger.getLogger(MakeSyncCallServlet.class.getName()); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String serviceName = req.getHeader(SERVICE_NAME); String methodName = req.getHeader(METHOD_NAME); String applicationId = req.getHeader(APPLICATION_ID); logger.info(applicationId + ":" + serviceName + "#" + methodName); if (validateParameters(resp, serviceName, methodName, applicationId) == false) { return; } byte[] requestBytes = IOUtils.toByteArray(req.getInputStream()); logger.info(applicationId + ":" + serviceName + "#" + methodName + ": requestBytes.length=" + (requestBytes != null ? requestBytes.length : "null")); byte[] responseBytes = null; try { @SuppressWarnings("unchecked") byte[] bytes = ApiProxy.getDelegate().makeSyncCall(ApiProxy.getCurrentEnvironment(), serviceName, methodName, requestBytes); responseBytes = bytes; } catch (Throwable th) { logger.log(Level.WARNING, applicationId + ":" + serviceName + "#" + methodName, th); resp.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR); resp.setContentType("text/plain"); th.printStackTrace(resp.getWriter()); resp.getWriter().flush(); return; } if (responseBytes == null) { logger.info(serviceName + "#" + methodName + ": responseBytes == null."); responseBytes = new byte[0]; } else { logger.info(serviceName + "#" + methodName + ": responseBytes.length=" + responseBytes.length); } resp.setContentType("application/octet-stream"); resp.getOutputStream().write(responseBytes); resp.getOutputStream().flush(); } private boolean validateParameters(HttpServletResponse resp, String serviceName, String methodName, String applicationId) throws IOException { if (StringUtils.isEmpty(serviceName)) { onErrorInParameters(resp, "serviceName was not specified."); return false; } if (StringUtils.isEmpty(methodName)) { onErrorInParameters(resp, "methodName was not specified."); return false; } if (StringUtils.isEmpty(applicationId)) { onErrorInParameters(resp, "applicationId was not specified."); return false; } // 念のためサーバ環境のApplicationIdと同じかどうか確認する。 String serverApplicationId = ApiProxy.getCurrentEnvironment().getAppId(); if (serverApplicationId.equals(applicationId) == false) { onErrorInParameters(resp, "applicationId was not equals to " + serverApplicationId + "."); return false; } return true; } private void onErrorInParameters(HttpServletResponse resp, String x) throws IOException { resp.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR); resp.setContentType("text/plain"); resp.getWriter().println(x); resp.getWriter().flush(); } }
MakeSyncCallServletDelegate
Http通信の手を抜くためにURLFetchServiceを使ってるけど、commons-httpclientに置き換えるつもり。
public class MakeSyncCallServletDelegate implements ApiProxy.Delegate<Environment> { @SuppressWarnings("unchecked") private final ApiProxy.Delegate<Environment> original = ApiProxy.getDelegate(); final URL url; MakeSyncCallServletDelegate(URL url) throws MalformedURLException { this.url = url; } public void log(Environment environment, LogRecord logRecord) { getOriginal().log(environment, logRecord); } public byte[] makeSyncCall(Environment environment, String serviceName, String methodName, byte[] request) throws ApiProxyException { if (serviceName.equals("urlfetch")) { return original.makeSyncCall(environment, serviceName, methodName, request); } URLFetchService service = URLFetchServiceFactory.getURLFetchService(); try { HTTPRequest httpRequest = new HTTPRequest(url, HTTPMethod.POST); httpRequest.addHeader(new HTTPHeader("serviceName", serviceName)); httpRequest.addHeader(new HTTPHeader("methodName", methodName)); httpRequest.addHeader(new HTTPHeader("applicationId", environment.getAppId())); httpRequest.setPayload(request); HTTPResponse httpResponse = service.fetch(httpRequest); if (httpResponse.getResponseCode() == 500) { System.out.println(new String(httpResponse.getContent())); return new byte[0]; } return httpResponse.getContent(); } catch (IOException e) { e.printStackTrace(); return new byte[0]; } } public ApiProxy.Delegate<Environment> getOriginal() { return original; } }
0 件のコメント:
コメントを投稿