昨日の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 件のコメント:
コメントを投稿