MeshMS Java API: send message

This commit is contained in:
Andrew Bettison 2014-06-19 17:27:05 +09:30
parent 819b8dc9e7
commit eba7f6555f
9 changed files with 208 additions and 41 deletions

View File

@ -27,9 +27,18 @@ import org.servalproject.servaldna.meshms.MeshMSMessageList;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.net.HttpURLConnection;
import org.servalproject.codec.Base64;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.ServalDCommand;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.meshms.MeshMSCommon;
import org.servalproject.servaldna.meshms.MeshMSConversationList;
import org.servalproject.servaldna.meshms.MeshMSMessageList;
import org.servalproject.servaldna.meshms.MeshMSException;
import org.servalproject.servaldna.meshms.MeshMSStatus;
public class ServalDClient implements ServalDHttpConnectionFactory
{
@ -63,6 +72,11 @@ public class ServalDClient implements ServalDHttpConnectionFactory
return list;
}
public MeshMSStatus meshmsSendMessage(SubscriberId sid1, SubscriberId sid2, String text) throws IOException, ServalDInterfaceException, MeshMSException
{
return MeshMSCommon.sendMessage(this, sid1, sid2, text);
}
// interface ServalDHttpConnectionFactory
public HttpURLConnection newServalDHttpConnection(String path) throws ServalDInterfaceException, IOException
{

View File

@ -21,8 +21,9 @@
package org.servalproject.servaldna;
/**
* Thrown when a request to a servald JNI method fails. This typically means that the returned
* status is non-zero, or some other result was returned that indicated the operation failed.
* Thrown when servald returns an error result from an operation, whether JNI or RESTful HTTP
* request, or MDP command. This typically means that the returned status is non-zero, or some
* other result was returned that indicated the operation failed.
*
* @author Andrew Bettison <andrew@servalproject.com>
*/

View File

@ -93,6 +93,12 @@ public class ServerControl {
if (!isRunning())
throw new ServalDInterfaceException("server is not running");
if (client==null) {
/* TODO: replace the following username/password configuration with a better scheme
* (outside the scope of this API) that does not require any invocations of the JNI, and
* allows any application (user) on the local host to request authorisation to use the
* RESTful interface. The authorisation must then be supplied to the restful client
* object before requests can be made.
*/
String restfulPassword = ServalDCommand.getConfigItem("rhizome.api.restful.users." + restfulUsername + ".password");
if (restfulPassword == null) {
String pwd = new BigInteger(130, new SecureRandom()).toString(32);

View File

@ -22,51 +22,101 @@ package org.servalproject.servaldna.meshms;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.net.HttpURLConnection;
import org.servalproject.servaldna.SubscriberId;
import org.servalproject.servaldna.ServalDHttpConnectionFactory;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServalDFailureException;
import org.servalproject.json.JSONTokeniser;
import org.servalproject.json.JSONInputException;
class MeshMSCommon
public class MeshMSCommon
{
protected static JSONTokeniser connectMeshMSRestful(HttpURLConnection conn) throws IOException, ServalDInterfaceException, MeshMSException
protected static JSONTokeniser receiveRestfulResponse(HttpURLConnection conn, int expected_response_code) throws IOException, ServalDInterfaceException, MeshMSException
{
conn.connect();
if (!conn.getContentType().equals("application/json"))
throw new ServalDInterfaceException("unexpected HTTP Content-Type: " + conn.getContentType());
if (conn.getResponseCode() == HttpURLConnection.HTTP_FORBIDDEN) {
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getErrorStream(), "US-ASCII"));
try {
json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("http_status_code");
json.consume(JSONTokeniser.Token.COLON);
json.consume(Integer.class);
json.consume(JSONTokeniser.Token.COMMA);
json.consume("http_status_message");
json.consume(JSONTokeniser.Token.COLON);
String message = json.consume(String.class);
json.consume(JSONTokeniser.Token.COMMA);
json.consume("meshms_status_code");
json.consume(JSONTokeniser.Token.COLON);
int meshms_status = json.consume(Integer.class);
json.consume(JSONTokeniser.Token.END_OBJECT);
json.consume(JSONTokeniser.Token.EOF);
switch (meshms_status) {
case 2:
throw new MeshMSUnknownIdentityException(conn.getURL());
case 3:
throw new MeshMSProtocolFaultException(conn.getURL());
}
throw new ServalDInterfaceException("unexpected MeshMS status = " + meshms_status + ", \"" + message + "\"");
}
catch (JSONInputException e) {
throw new ServalDInterfaceException("malformed response body for HTTP status code " + conn.getResponseCode(), e);
}
Status status = decodeRestfulStatus(json);
throwRestfulResponseExceptions(status, conn.getURL());
throw new ServalDInterfaceException("unexpected MeshMS status = " + status.meshms_status + ", \"" + status.message + "\"");
}
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK)
if (conn.getResponseCode() != expected_response_code)
throw new ServalDInterfaceException("unexpected HTTP response code: " + conn.getResponseCode());
JSONTokeniser json = new JSONTokeniser(new InputStreamReader(conn.getInputStream(), "US-ASCII"));
return json;
}
private static class Status {
public MeshMSStatus meshms_status;
public String message;
}
protected static Status decodeRestfulStatus(JSONTokeniser json) throws IOException, ServalDInterfaceException
{
try {
Status status = new Status();
json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("http_status_code");
json.consume(JSONTokeniser.Token.COLON);
json.consume(Integer.class);
json.consume(JSONTokeniser.Token.COMMA);
status.message = json.consume("http_status_message");
json.consume(JSONTokeniser.Token.COLON);
String message = json.consume(String.class);
json.consume(JSONTokeniser.Token.COMMA);
json.consume("meshms_status_code");
json.consume(JSONTokeniser.Token.COLON);
status.meshms_status = MeshMSStatus.fromCode(json.consume(Integer.class));
json.consume(JSONTokeniser.Token.END_OBJECT);
json.consume(JSONTokeniser.Token.EOF);
return status;
}
catch (JSONInputException e) {
throw new ServalDInterfaceException("malformed JSON status response", e);
}
}
protected static void throwRestfulResponseExceptions(Status status, URL url) throws MeshMSException, ServalDFailureException
{
switch (status.meshms_status) {
case OK:
case UPDATED:
break;
case SID_LOCKED:
throw new MeshMSUnknownIdentityException(url);
case PROTOCOL_FAULT:
throw new MeshMSProtocolFaultException(url);
case ERROR:
throw new ServalDFailureException("received meshms_status=ERROR(-1) from " + url);
}
}
public static MeshMSStatus sendMessage(ServalDHttpConnectionFactory connector, SubscriberId sid1, SubscriberId sid2, String text) throws IOException, ServalDInterfaceException, MeshMSException
{
HttpURLConnection conn = connector.newServalDHttpConnection("/restful/meshms/" + sid1.toHex() + "/" + sid2.toHex() + "/sendmessage");
String boundary = Long.toHexString(System.currentTimeMillis());
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
conn.connect();
OutputStream ost = conn.getOutputStream();
PrintStream wr = new PrintStream(ost, false, "US-ASCII");
wr.print("--" + boundary + "\r\n");
wr.print("Content-Disposition: form-data; name=\"message\"\r\n");
wr.print("Content-Type: text/plain; charset=utf-8\r\n");
wr.print("\r\n");
wr.print(text);
wr.print("\r\n--" + boundary + "--\r\n");
wr.close();
JSONTokeniser json = MeshMSCommon.receiveRestfulResponse(conn, HttpURLConnection.HTTP_CREATED);
Status status = decodeRestfulStatus(json);
throwRestfulResponseExceptions(status, conn.getURL());
return status.meshms_status;
}
}

View File

@ -69,7 +69,8 @@ public class MeshMSConversationList {
columnIndex_read_offset = -1;
rowCount = 0;
httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + sid.toHex() + "/conversationlist.json");
json = MeshMSCommon.connectMeshMSRestful(httpConnection);
httpConnection.connect();
json = MeshMSCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("header");
json.consume(JSONTokeniser.Token.COLON);

View File

@ -79,7 +79,8 @@ public class MeshMSMessageList {
columnIndex_ack_offset = -1;
rowCount = 0;
httpConnection = httpConnector.newServalDHttpConnection("/restful/meshms/" + my_sid.toHex() + "/" + their_sid.toHex() + "/messagelist.json");
json = MeshMSCommon.connectMeshMSRestful(httpConnection);
httpConnection.connect();
json = MeshMSCommon.receiveRestfulResponse(httpConnection, HttpURLConnection.HTTP_OK);
json.consume(JSONTokeniser.Token.START_OBJECT);
json.consume("read_offset");
json.consume(JSONTokeniser.Token.COLON);

View File

@ -0,0 +1,63 @@
/**
* Copyright (C) 2014 Serval Project Inc.
*
* This file is part of Serval Software (http://www.servalproject.org)
*
* Serval Software is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This source code is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this source code; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.servalproject.servaldna.meshms;
import org.servalproject.servaldna.ServalDInterfaceException;
/* This enum is a direct isomorphism from the C "enum meshms_result" defined in meshms.h.
*/
public enum MeshMSStatus {
ERROR(-1), // unexpected error (underlying failure)
OK(0), // operation succeeded, no bundle changed
UPDATED(1), // operation succeeded, bundle updated
SID_LOCKED(2), // cannot decode or send messages for that SID
PROTOCOL_FAULT(3), // missing or faulty ply bundle
;
final public int code;
private MeshMSStatus(int code) {
this.code = code;
}
public static MeshMSStatus fromCode(int code) throws InvalidException
{
MeshMSStatus status = null;
switch (code) {
case -1: status = ERROR; break;
case 0: status = OK; break;
case 1: status = UPDATED; break;
case 2: status = SID_LOCKED; break;
case 3: status = PROTOCOL_FAULT; break;
default: throw new InvalidException(code);
}
assert status.code == code;
return status;
}
public static class InvalidException extends ServalDInterfaceException
{
public InvalidException(int code) {
super("invalid MeshMS status code = " + code);
}
}
}

View File

@ -20,6 +20,7 @@
package org.servalproject.test;
import java.io.IOException;
import org.servalproject.servaldna.ServalDClient;
import org.servalproject.servaldna.ServalDInterfaceException;
import org.servalproject.servaldna.ServerControl;
@ -29,8 +30,7 @@ import org.servalproject.servaldna.meshms.MeshMSConversationList;
import org.servalproject.servaldna.meshms.MeshMSException;
import org.servalproject.servaldna.meshms.MeshMSMessage;
import org.servalproject.servaldna.meshms.MeshMSMessageList;
import java.io.IOException;
import org.servalproject.servaldna.meshms.MeshMSStatus;
public class Meshms {
@ -94,6 +94,19 @@ public class Meshms {
System.exit(0);
}
static void meshms_send_message(SubscriberId sid1, SubscriberId sid2, String text) throws ServalDInterfaceException, IOException, InterruptedException
{
ServalDClient client = new ServerControl().getRestfulClient();
try {
MeshMSStatus status = client.meshmsSendMessage(sid1, sid2, text);
System.out.println("" + status);
}
catch (MeshMSException e) {
System.out.println(e.toString());
}
System.exit(0);
}
public static void main(String... args)
{
if (args.length < 1)
@ -104,6 +117,8 @@ public class Meshms {
meshms_list_conversations(new SubscriberId(args[1]));
else if (methodName.equals("meshms-list-messages"))
meshms_list_messages(new SubscriberId(args[1]), new SubscriberId(args[2]));
else if (methodName.equals("meshms-send-message"))
meshms_send_message(new SubscriberId(args[1]), new SubscriberId(args[2]), args[3]);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);

View File

@ -137,14 +137,30 @@ test_MeshmsListMessages() {
}
doc_MeshmsListMessagesNoIdentity="Java API list MeshMS messages from unknown identity"
setup_MeshmsListMessagesNoIdentity() {
setup
SIDX=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
}
test_MeshmsListMessagesNoIdentity() {
SIDX=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
executeJavaOk org.servalproject.test.Meshms meshms-list-messages $SIDX $SIDA2
assertStdoutGrep 'MeshMSUnknownIdentityException'
tfw_cat --stdout --stderr
}
doc_MeshmsSend="Java API send MeshMS message"
test_MeshmsSend() {
executeJavaOk org.servalproject.test.Meshms meshms-send-message $SIDA1 $SIDA2 "Hello World"
executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutGrep --matches=1 ':>:Hello World'
executeJavaOk org.servalproject.test.Meshms meshms-send-message $SIDA2 $SIDA1 "Hello Back!"
executeOk_servald meshms list messages $SIDA1 $SIDA2
assertStdoutGrep --matches=1 ':>:Hello World$'
assertStdoutGrep --matches=1 ':<:Hello Back!$'
}
doc_MeshmsSendNoIdentity="Java API send MeshMS message from unknown identity"
test_MeshmsSendNoIdentity() {
SIDX=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
executeJavaOk org.servalproject.test.Meshms meshms-send-message $SIDX $SIDA2 "Hello World"
assertStdoutGrep 'MeshMSUnknownIdentityException'
tfw_cat --stdout --stderr
}
runTests "$@"