Skip to content

Commit

Permalink
New feature : capability to send HTTP request (e.g. to Web service)
Browse files Browse the repository at this point in the history
  • Loading branch information
benoitgaudou committed May 6, 2022
1 parent 14dee4e commit e2585a2
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 0 deletions.
@@ -0,0 +1,56 @@
/**
* Name: HTTPGET
* Based on the internal empty template.
* Author: benoitgaudou
* Tags:
*/


model HTTPGET

global {
int port <- 443; // for HTPP : 80 http, for HTTPS : 443
string url <- "https://openlibrary.org";

init {
create NetworkingAgent number: 1 {
do connect to: url protocol: "http" port: port raw: true;
}
}

}

species NetworkingAgent skills:[network] {

reflex send when: cycle = 0 {
write "sending message ";
do send to: "/search/authors.json?q=j%20k%20rowling" contents: ["GET"];
}

reflex get_message {
loop while:has_more_message()
{
//read a message
message mess <- fetch_message();
//display the message
write name + " fecth this message: " + mess.contents;
write sample(map(mess.contents)["CODE"]);
write sample(map(mess.contents)["BODY"]);
write sample(map(mess.contents)["HEADERS"]);
}

}


aspect default {
draw circle(1) color: #red border: #black;
}
}

experiment Server_testdd type: gui {
output {
display d {
species NetworkingAgent;
}
}
}
@@ -0,0 +1,60 @@
/**
* Name: HTTPGET
* Based on the internal empty template.
* Author: benoitgaudou
* Tags:
*/


model HTTPGET

global {
int port <- 8989; // for HTPP : 80 http, for HTTPS : 443
string url <- "localhost";

init {
create NetworkingAgent number: 1 {
do connect to: url protocol: "http" port: port raw: true;
}
}

}

species NetworkingAgent skills:[network] {

reflex send when: cycle = 0 {
write "sending message ";

// do send to: "/api/user/" contents: ["POST",map(["toto"::34,"titi"::12]), map(["Content-Type"::"application/json"])];
// do send to: "/api/user/" contents: ["PUT",map(["toto"::34,"titi"::12]), map(["Content-Type"::"application/json"])];
do send to: "/api/user/" contents: ["DELETE"];

}

reflex get_message {
loop while:has_more_message()
{
//read a message
message mess <- fetch_message();
//display the message
write name + " fecth this message: " + mess.contents;
write sample(map(mess.contents)["CODE"]);
write sample(map(mess.contents)["BODY"]);
write sample(map(mess.contents)["HEADERS"]);
}

}


aspect default {
draw circle(1) color: #red border: #black;
}
}

experiment Server_testdd type: gui {
output {
display d {
species NetworkingAgent;
}
}
}
@@ -0,0 +1,172 @@
package ummisco.gama.network.httprequest;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.Builder;

import msi.gama.extensions.messaging.GamaMailbox;
import msi.gama.extensions.messaging.GamaMessage;
import msi.gama.extensions.messaging.MessagingSkill;
import msi.gama.metamodel.agent.IAgent;
import msi.gama.runtime.IScope;
import msi.gama.util.GamaList;
import msi.gama.util.IMap;
import msi.gama.util.file.json.Jsoner;
import ummisco.gama.network.common.Connector;
import ummisco.gama.network.common.GamaNetworkException;
import ummisco.gama.network.common.socket.SocketService;
import ummisco.gama.network.httprequest.utils.Utils;

public class HTTPRequestConnector extends Connector {

/** The timeout. */
public static Integer DEFAULT_TIMEOUT = 5000;

/** The default host. */
public static String DEFAULT_HOST = "localhost";

/** The default port. */
public static String DEFAULT_PORT = "80";


/** The ss thread. */
// MultiThreadedArduinoReceiver ssThread;

String host;
String port;

//
private HttpRequest request;

/**
* Instantiates a new HTTPRequest connector.
*
* @param scope the scope
*/
public HTTPRequestConnector(final IScope scope) {
}

@Override
protected void connectToServer(IAgent agent) throws GamaNetworkException {
String host_tmp = this.getConfigurationParameter(SERVER_URL);
String port_tmp = this.getConfigurationParameter(SERVER_PORT);

host = host_tmp == null ? DEFAULT_HOST : host_tmp;
port = port_tmp == null ? DEFAULT_PORT : port_tmp;
}

@Override
protected boolean isAlive(IAgent agent) throws GamaNetworkException {
// TODO Auto-generated method stub
return false;
}

@Override
protected void subscribeToGroup(IAgent agt, String boxName) throws GamaNetworkException {
// TODO Auto-generated method stub

}

@Override
protected void unsubscribeGroup(IAgent agt, String boxName) throws GamaNetworkException {
// TODO Auto-generated method stub

}

@Override
protected void releaseConnection(IScope scope) throws GamaNetworkException {
// TODO Auto-generated method stub

}

@Override
public void send(final IAgent sender, final String receiver, final GamaMessage content) {
Object cont = content.getContents(sender.getScope());
try {
if(cont instanceof GamaList) {
URI uri = null;
try {
uri = Utils.buildURI(host,""+port,receiver);
} catch (URISyntaxException e) {
e.printStackTrace();
}
Builder requestBuilder = HttpRequest.newBuilder().uri(uri);

// Management of the content. A list [method,body,headers] or [method] are expected
GamaList listContent = (GamaList) cont;
String method = (String) listContent.get(0);
String jsonBody = (listContent.size() > 1) ? Jsoner.serialize(listContent.get(1)) : "";
IMap<String,String> header = (listContent.size() > 2) ? (IMap<String,String>) listContent.get(2) : null;

if(header != null) {
for(String key : header.keySet()) {
requestBuilder.header(key, header.get(key));
}
}

switch(method) {
case "GET":
request = requestBuilder.GET().build();
break;
case "POST":
request = requestBuilder.POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build();
break;
case "PUT":
request = requestBuilder.PUT(HttpRequest.BodyPublishers.ofString(jsonBody)).build();
break;
case "DELETE":
request = requestBuilder.DELETE().build();
break;
default:
throw GamaNetworkException.cannotSendMessage(null, "Bad HTTP action");
}

this.sendMessage(sender, receiver, jsonBody);

} else {
throw GamaNetworkException.cannotSendMessage(null, "The content expected to be sent is well formatted, a list [method,body,headers] is expected.");
}
} catch (Exception e) {
e.printStackTrace();
}
}


@Override
protected void sendMessage(IAgent sender, String receiver, String content) throws GamaNetworkException {

try {

HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());

// Manage the response of the request
IMap<String,Object> responseMap = Utils.formatResponse(response);

@SuppressWarnings ("unchecked")
GamaMailbox<GamaMessage> mailbox =
(GamaMailbox<GamaMessage>) sender.getAttribute(MessagingSkill.MAILBOX_ATTRIBUTE);
if (mailbox == null) {
mailbox = new GamaMailbox<>();
sender.setAttribute(MessagingSkill.MAILBOX_ATTRIBUTE, mailbox);
}

GamaMessage msg = new GamaMessage(sender.getScope(), "HTTP", sender.getName(), responseMap);

mailbox.add(msg);

} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

@Override
public SocketService getSocketService() {
return null;
}

}
@@ -0,0 +1,58 @@
package ummisco.gama.network.httprequest.utils;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpResponse;
import java.util.List;
import java.util.Map;

import msi.gama.runtime.IScope;
import msi.gama.util.GamaMapFactory;
import msi.gama.util.IList;
import msi.gama.util.IMap;
import msi.gama.util.file.json.DeserializationException;
import msi.gama.util.file.json.Jsoner;
import msi.gaml.types.Types;

public class Utils {
public static URI buildURI(final String host, final String port, final String url) throws URISyntaxException {
String uri = "" ;
String local_port = ( (port != null) ? (":"+port) : "");

if(host.startsWith("http://") || host.startsWith("https://")) {
uri = host + local_port + url;
} else {
uri = "http://"+host + local_port + url;
}

return new URI(uri);//URLEncoder.encode(uri, StandardCharsets.UTF_8));
}

public static IList parseBODY(final IScope scope, final String body) {
// TODO Transform lee boy en map/list si response en JSON

return null;
}


public static IMap<String,Object> formatResponse(HttpResponse<String> response){
IMap<String,Object> responseMap = null;

try {
responseMap = GamaMapFactory.create();

responseMap.put("CODE", response.statusCode());

Object jsonBody = ("".equals(response.body())) ? "" : Jsoner.deserialize(response.body());
responseMap.put("BODY", jsonBody);

IMap<String, List<String>> mapHeaders = GamaMapFactory.wrap(Types.STRING, Types.STRING, false, (Map<String,List<String>>) response.headers().map());
responseMap.put("HEADERS",mapHeaders);
} catch (DeserializationException e) {
e.printStackTrace();
}

return responseMap;
}

}
Expand Up @@ -66,6 +66,9 @@ public interface INetworkSkill {
/** The tcp client. */
String TCP_CLIENT = "tcp_client";

/** For HTTP requests. */
String HTTP_REQUEST = "http";

/** The network skill. */
///// SKILL NETWORK
String NETWORK_SKILL = "network";
Expand Down
Expand Up @@ -41,6 +41,7 @@
import ummisco.gama.dev.utils.DEBUG;
import ummisco.gama.network.common.ConnectorMessage;
import ummisco.gama.network.common.IConnector;
import ummisco.gama.network.httprequest.HTTPRequestConnector;
import ummisco.gama.network.mqtt.MQTTConnector;
import ummisco.gama.network.serial.ArduinoConnector;
import ummisco.gama.network.tcp.TCPConnector;
Expand Down Expand Up @@ -234,6 +235,10 @@ public boolean connectToServer(final IScope scope) throws GamaRuntimeException {
connector.configure(IConnector.SERVER_PORT, "" + port);
} else if ("arduino".equals(protocol)) {
connector = new ArduinoConnector(scope);
} else if (INetworkSkill.HTTP_REQUEST.equals(protocol)) {
connector = new HTTPRequestConnector(scope);
connector.configure(IConnector.SERVER_URL, serverURL);
connector.configure(IConnector.SERVER_PORT, "" + port);
} else // if(protocol.equals( INetworkSkill.MQTT))
{
DEBUG.OUT("create MQTT serveur " + login + " " + password);
Expand Down

0 comments on commit e2585a2

Please sign in to comment.