org.red5.client.net.remoting.DSRemotingClient Maven / Gradle / Ivy
/*
* RED5 Open Source Flash Server - https://github.com/Red5/ Copyright 2006-2015 by respective authors (see below). All rights reserved. Licensed under the Apache License, Version
* 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless
* required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package org.red5.client.net.remoting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.util.EntityUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.compatibility.flex.messaging.messages.AcknowledgeMessage;
import org.red5.compatibility.flex.messaging.messages.AcknowledgeMessageExt;
import org.red5.compatibility.flex.messaging.messages.AsyncMessageExt;
import org.red5.compatibility.flex.messaging.messages.CommandMessage;
import org.red5.compatibility.flex.messaging.messages.Constants;
import org.red5.compatibility.flex.messaging.messages.Message;
import org.red5.io.amf3.Input;
import org.red5.io.amf3.Output;
import org.red5.io.object.Deserializer;
import org.red5.io.object.RecordSet;
import org.red5.io.object.Serializer;
import org.red5.io.utils.ObjectMap;
import org.red5.server.net.remoting.RemotingClient;
import org.red5.server.net.remoting.RemotingHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Client interface for remoting calls directed at an LCDS or BlazeDS style service.
*
* @author The Red5 Project
* @author Paul Gregoire ([email protected])
*/
public class DSRemotingClient extends RemotingClient {
protected static Logger log = LoggerFactory.getLogger(DSRemotingClient.class);
/** The datasource id (assigned by the server). DsId */
private String dataSourceId = "nil";
/** The request sequence number */
private int sequenceCounter = 1;
/**
* Dummy constructor used by the spring configuration.
*/
public DSRemotingClient() {
log.debug("DSRemotingClient created");
}
/**
* Create new remoting client for the given url.
*
* @param url
* URL to connect to
*/
public DSRemotingClient(String url) {
super(url, DEFAULT_TIMEOUT);
log.debug("DSRemotingClient created - url: {}", url);
}
public String getDataSourceId() {
return dataSourceId;
}
public void setDataSourceId(String dataSourceId) {
this.dataSourceId = dataSourceId;
}
/**
* Encode the method call.
*
* @param method
* Remote method being called
* @param params
* Method parameters
* @return Byte buffer with data to perform remoting call
*/
private IoBuffer encodeInvoke(String method, Object[] params) {
log.debug("RemotingClient encodeInvoke - method: {} params: {}", method, params);
IoBuffer result = IoBuffer.allocate(1024);
result.setAutoExpand(true);
//force version 3
result.putShort((short) 3);
// Headers
Collection hdr = headers.values();
result.putShort((short) hdr.size());
for (RemotingHeader header : hdr) {
Output.putString(result, header.getName());
result.put(header.getMustUnderstand() ? (byte) 0x01 : (byte) 0x00);
IoBuffer tmp = IoBuffer.allocate(1024);
tmp.setAutoExpand(true);
Output tmpOut = new Output(tmp);
Serializer.serialize(tmpOut, header.getValue());
tmp.flip();
// Size of header data
result.putInt(tmp.limit());
// Header data
result.put(tmp);
tmp.free();
tmp = null;
}
// One body
result.putShort((short) 1);
// Method name
Output.putString(result, method);
// Client callback for response
//Output.putString(result, "");
//responseURI
Output.putString(result, "/" + sequenceCounter++);
// Serialize parameters
IoBuffer tmp = IoBuffer.allocate(1024);
tmp.setAutoExpand(true);
Output tmpOut = new Output(tmp);
//if the params are null send the NULL AMF type
//this should fix APPSERVER-296
if (params == null) {
tmpOut.writeNull();
} else {
tmpOut.writeArray(params);
}
tmp.flip();
// Store size and parameters
result.putInt(tmp.limit());
result.put(tmp);
tmp.free();
tmp = null;
result.flip();
return result;
}
/**
* Process any headers sent in the response.
*
* @param in
* Byte buffer with response data
*/
@SuppressWarnings("null")
@Override
protected void processHeaders(IoBuffer in) {
log.debug("RemotingClient processHeaders - buffer limit: {}", (in != null ? in.limit() : 0));
int version = in.getUnsignedShort(); // skip
log.debug("Version: {}", version);
// the version by now, AMF3 is not yet implemented
int count = in.getUnsignedShort();
log.debug("Count: {}", count);
Input input = new Input(in);
for (int i = 0; i < count; i++) {
String name = input.getString();
log.debug("Name: {}", name);
boolean required = (in.get() == 0x01);
log.debug("Required: {}", required);
Object value = null;
int len = in.getInt();
log.debug("Length: {}", len);
// look for junk bytes ff,ff,ff,ff
if (len == -1) {
in.get(); //02
len = in.getShort();
log.debug("Corrected length: {}", len);
value = input.readString(len);
} else {
value = Deserializer.deserialize(input, Object.class);
}
log.debug("Value: {}", value);
// XXX: this is pretty much untested!!!
if (RemotingHeader.APPEND_TO_GATEWAY_URL.equals(name)) {
// Append string to gateway url
appendToUrl = (String) value;
} else if (RemotingHeader.REPLACE_GATEWAY_URL.equals(name)) {
// Replace complete gateway url
url = (String) value;
// XXX: reset the ) {
@SuppressWarnings("unchecked")
Map valueMap = (Map) value;
RemotingHeader header = new RemotingHeader((String) valueMap.get("name"), (Boolean) valueMap.get("mustUnderstand"), valueMap.get("data"));
headers.put(header.getName(), header);
} else {
log.error("Expected Map but received {}", value);
}
} else {
log.warn("Unsupported remoting header \"{}\" received with value \"{}\"", name, value);
}
}
}
/**
* Decode response received from remoting server.
*
* @param data
* Result data to decode
* @return Object deserialized from byte buffer data
*/
@SuppressWarnings("null")
private Object decodeResult(IoBuffer data) {
log.debug("decodeResult - data limit: {}", (data != null ? data.limit() : 0));
processHeaders(data);
Input input = new Input(data);
String target = null;
byte b = data.get();
//look for SOH
if (b == 0) {
log.debug("NUL: {}", b); //0
log.debug("SOH: {}", data.get()); //1
} else if (b == 1) {
log.debug("SOH: {}", b); //1
}
int targetUriLength = data.getShort();
log.debug("targetUri length: {}", targetUriLength);
target = input.readString(targetUriLength);
log.debug("NUL: {}", data.get()); //0
//should be junk bytes ff, ff, ff, ff
int count = data.getInt();
if (count == -1) {
log.debug("DC1: {}", data.get()); //17
count = 1;
} else {
data.position(data.position() - 4);
count = data.getShort();
}
if (count != 1) {
throw new RuntimeException("Expected exactly one result but got " + count);
}
String[] targetParts = target.split("[/]");
if (targetParts.length > 1) {
log.debug("Result sequence number: {}", targetParts[1]);
target = targetParts[2];
} else {
target = target.substring(1);
}
log.debug("Target: {}", target);
if ("onResult".equals(target)) {
//read return value
return input.readObject();
} else if ("onStatus".equals(target)) {
//read return value
return input.readObject();
}
//read return value
return Deserializer.deserialize(input, Object.class);
}
/**
* Invoke a method synchronously on the remoting server.
*
* @param method
* Method name
* @param params
* Parameters passed to method
* @return the result of the method call
*/
@SuppressWarnings("null")
@Override
public Object invokeMethod(String method, Object[] params) {
log.debug("invokeMethod url: {}", (url + appendToUrl));
IoBuffer resultBuffer = null;
IoBuffer data = encodeInvoke(method, params);
//setup POST
HttpPost post = null;
try {
post = new HttpPost(url + appendToUrl);
post.addHeader("Content-Type", CONTENT_TYPE);
post.setEntity(new InputStreamEntity(data.asInputStream(), data.limit()));
// execute the method
HttpResponse response = client.execute(post);
int code = response.getStatusLine().getStatusCode();
log.debug("HTTP response code: {}", code);
if (code / 100 != 2) {
throw new RuntimeException("Didn't receive success from remoting server");
} else {
HttpEntity entity = response.getEntity();
if (entity != null) {
//fix for Trac #676
int contentLength = (int) entity.getContentLength();
//default the content length to 16 if post doesn't contain a good value
if (contentLength < 1) {
contentLength = 16;
}
// get the response as bytes
byte[] bytes = EntityUtils.toByteArray(entity);
resultBuffer = IoBuffer.wrap(bytes);
resultBuffer.flip();
Object result = decodeResult(resultBuffer);
if (result instanceof RecordSet) {
// Make sure we can retrieve paged results
((RecordSet) result).setRemotingClient(this);
}
return result;
}
}
} catch (Exception ex) {
log.error("Error while invoking remoting method.", ex);
post.abort();
} finally {
if (resultBuffer != null) {
resultBuffer.free();
resultBuffer = null;
}
data.free();
data = null;
}
return null;
}
/**
* Used for debugging byte stream.
*
* @param data
* IoBuffer
*/
@SuppressWarnings("unused")
private static final void dump(IoBuffer data) {
log.debug("Hex: {}", data.getHexDump());
int pos = data.position();
byte[] bar = new byte[data.limit() - data.position()];
data.get(bar);
log.debug("Str {}", new String(bar));
bar = null;
data.position(pos);
}
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
//blazeds my-polling-amf http://localhost:8400/meta/messagebroker/amfpolling
DSRemotingClient client = new DSRemotingClient("http://localhost:8400/meta/messagebroker/amfpolling");
try {
//send ping
CommandMessage msg = new CommandMessage();
msg.setCorrelationId("");
msg.setDestination("");
//create / set headers
ObjectMap headerMap = new ObjectMap();
headerMap.put(Message.FLEX_CLIENT_ID_HEADER, "nil");
headerMap.put(Message.MESSAGING_VERSION, 1);
msg.setHeaders(headerMap);
msg.setOperation(Constants.CLIENT_PING_OPERATION);
msg.setBody(new Object[] {});
Object response = client.invokeMethod("null", new Object[] { msg });
log.debug("Response: {}\n{}", response.getClass().getName(), response);
if (response instanceof AcknowledgeMessage || response instanceof AcknowledgeMessageExt) {
log.info("Got first ACK");
AcknowledgeMessage ack = (AcknowledgeMessage) response;
Object id = ack.getHeader(Message.FLEX_CLIENT_ID_HEADER);
if (id != null) {
log.info("Got DSId: {}", id);
client.setDataSourceId((String) id);
}
}
//wait a second for a dsid
do {
Thread.sleep(1000);
log.info("Done with sleeping");
} while (client.getDataSourceId().equals("nil"));
//send subscribe
msg = new CommandMessage();
msg.setCorrelationId("");
msg.setDestination("Red5Chat");
headerMap = new ObjectMap();
headerMap.put(Message.FLEX_CLIENT_ID_HEADER, client.getDataSourceId());
headerMap.put(Message.ENDPOINT_HEADER, "my-polling-amf");
msg.setHeaders(headerMap);
msg.setOperation(Constants.SUBSCRIBE_OPERATION);
msg.setBody(new Object[] {});
response = client.invokeMethod("null", new Object[] { msg });
if (response instanceof AcknowledgeMessage || response instanceof AcknowledgeMessageExt) {
log.info("Got second ACK {}", ((AcknowledgeMessage) response));
}
//poll every 5 seconds for 60
int loop = 12;
do {
Thread.sleep(5000);
log.info("Done with sleeping");
//send poll
//0 messages - returns DSK
//n messages - CommandMessage with internal DSA
msg = new CommandMessage();
msg.setCorrelationId("");
msg.setDestination("Red5Chat");
headerMap = new ObjectMap();
headerMap.put(Message.FLEX_CLIENT_ID_HEADER, client.getDataSourceId());
msg.setHeaders(headerMap);
msg.setOperation(Constants.POLL_OPERATION);
msg.setBody(new Object[] {});
response = client.invokeMethod("null", new Object[] { msg });
if (response instanceof AcknowledgeMessage) {
AcknowledgeMessage ack = (AcknowledgeMessage) response;
log.info("Got ACK response {}", ack);
} else if (response instanceof CommandMessage) {
CommandMessage com = (CommandMessage) response;
log.info("Got COM response {}", com);
ArrayList list = (ArrayList) com.getBody();
log.info("Child message body: {}", ((AsyncMessageExt) list.get(0)).getBody());
}
} while (--loop > 0);
} catch (Exception e) {
log.warn("Exception {}", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy