RESTful web services are the most common type of web services today.
Despite being widely used, then many developers do not understand the characteristics of RESTful web services.
Using JSON or lightweight XML is not sufficient for a web service to be a RESTful style web service.
The two fundamen different web srevice styles are:
RPC style is easy to understand. A web service define operations with arguments and return values. This matches functions/methods in programming languages.
RESTful style is different. A web service manage a resource (a collection of items). The only available operations are those defined by the HTTP protocol doing basic CRUD: GET (Read), POST (Create), PUT (Update), DELETE (Delete) and PATCH (Update).
GET reads either an item in a collection or the multiple (possible all) items in a collection, POST creates a new item in the collection, PUT updates an existing item in the collection and DELETE removes an item from the collection.
At first this may seem a little weird, but the model actually works fine in a lot of business applications. But if the model does not work for your problem and you really need RPC style, then you should not try and fake RESTful style but instead go with RPC style like SOAP, XML-RPC or JSON-RPC.
RPC style solutions are covered in:
RESTful web services can be implemented in many languages: Java, C#, Python, PHP etc., but we will look at Java here. On VMS.
JAX-RS is the Java API for RESTful web services.
JAX-RS 1.1 was part of Java EE (2009).
There are multiple implementations of JAX-RS. Two of the most common are:
This article will use Jersey.
I have tested with Jersey version 1.2 (JAX-RS API 1.1), because that works with both Java 5 and Java 8.
For standalone server (not Tomcat) an embedded servlet container is needed. I have tested with Jetty 7.6.21, because that works with both Java 5 and Java 8.
You can use other (newer) versions as long as they are supported by the Java version available. Java 5 on Alpha is 20 years old and Java 8 on Itanium/x86-64 is 10 years old.
The specific files I have tested with are:
activation-1_1_1.jar asm-3_1.jar config-1_4_3.jar gson-2_2_4.jar jackson-core-asl-1_1_1.jar jaxb-api-2_1.jar jaxb-impl-2_1_12.jar jersey-bundle-1_2.jar jersey-client-1_2.jar jersey-core-1_2.jar jersey-json-1_2.jar jersey-server-1_2.jar jettison-1_1.jar jetty-client-7_6_21.jar jetty-continuation-7_6_21.jar jetty-http-7_6_21.jar jetty-io-7_6_21.jar jetty-security-7_6_21.jar jetty-server-7_6_21.jar jetty-servlet-7_6_21.jar jetty-servlets-7_6_21.jar jetty-util-7_6_21.jar jsr311-api-1_1_1.jar servlet-api-2_5.jar slf4j-api-1_6_1.jar slf4j-jdk14-1_6_1.jar stax-api-1_0-2.jar
(note that I converted file names to be ODS-2 friendly aka underscore instead of period)
Nothing in the Java code below or the referenced jar files are VMS specific. The exact same things runs on Linux and Windows. It is just the setup that are VMS specific. But that setup is important, because there are a gazillion tools and tutorials available for Linux and Windows - not much for VMS.
JAX-RS is annotation based:
The examples will use JSON only as JSON is way more common than XML today for RESTful web services.
The business logic will be simulated by a DataModel class that simulates the basic CRUD operations.
For more JAX-RS examples see the articles Web Service - RESTful and Web Service - Standalone.
Running the web service in a standalone server makes sense when:
Let us see an example.
package server;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Data {
private int iv;
private double xv;
private String sv;
public Data() {
this(0, 0.0, "");
}
public Data(int iv, double xv, String sv) {
super();
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
}
package server;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
@Path("/data")
public class DataService {
@Context
private HttpServletResponse response;
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/{v}")
public Data getOne(@PathParam("v") int v) {
return DataModel.getInstance().getOne(v);
}
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("")
public List<Data> getSome(@DefaultValue("-1") @QueryParam("start") int start, @DefaultValue("-1") @QueryParam("finish") int finish) {
if(start == -1 && finish == -1) {
return DataModel.getInstance().getAll();
} else {
return DataModel.getInstance().getSome(start, finish);
}
}
@POST
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Path("")
public Data add(Data o) {
DataModel.getInstance().add(o);
return o;
}
@PUT
@Consumes({MediaType.APPLICATION_JSON})
@Path("/{v}")
public void update(@PathParam("v") int v, Data o) {
if(v == o.getIv()) {
DataModel.getInstance().update(o);
}
}
@DELETE
@Path("/{v}")
public void remove(@PathParam("v") int v) {
DataModel.getInstance().remove(v);
}
}
package server;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class DataModel {
private static DataModel instance;
private Map<Integer,Data> data = null;
private DataModel() {
data = new TreeMap<Integer,Data>();
data.put(1, new Data(1, 1.1, "Text #1"));
data.put(2, new Data(2, 2.2, "Text #2"));
data.put(3, new Data(3, 3.3, "Text #3"));
}
public static synchronized DataModel getInstance() {
if(instance == null) {
instance = new DataModel();
}
return instance;
}
public synchronized Data getOne(int v) {
return data.get(v);
}
public synchronized List<Data> getAll() {
List<Data> res = new ArrayList<Data>();
for(Data d : data.values()) {
res.add(d);
}
return res;
}
public synchronized List<Data> getSome(int start, int finish) {
List<Data> res = new ArrayList<Data>();
for(Data d : data.values()) {
if(start <= d.getIv() & d.getIv() <= finish) {
res.add(d);
}
}
return res;
}
public synchronized void add(Data d) {
data.put(d.getIv(), d);
}
public synchronized void update(Data d) {
data.put(d.getIv(), d);
}
public synchronized void remove(int v) {
data.remove(v);
}
}
package server;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import com.sun.jersey.spi.container.servlet.ServletContainer;
public class ServerMain {
private static final int PORT = 8080;
private static final String CONTEXT = "/";
private static final String API = "/api/*";
public static void main(String[] args) throws Exception {
Server server = new Server(PORT);
ServletContextHandler ctx = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
ctx.setContextPath(CONTEXT);
server.setHandler(ctx);
ServletHolder srvlet = ctx.addServlet(ServletContainer.class, API);
srvlet.setInitOrder(1);
srvlet.setInitParameter("com.sun.jersey.config.property.packages", "server");
srvlet.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
server.start();
server.join();
}
}
$ jaxrslibloc = "DISK2:[ARNE.jaxrs]" ! change location
$ cp = f$parse("[]") - ".;"
$ loop:
$ jar = f$search("''jaxrslibloc'*.jar")
$ if jar .eqs. "" then goto endloop
$ cp = cp + "," + (jar - ";1")
$ goto loop
$ endloop:
$ define/nolog java$classpath 'cp'
$ exit
$ @cp
$ set def [.server]
$ javac *.java
$ set def [-]
$ java "server.ServerMain"
The service is available at URL:
Running the web service in Tomcat makes sense when:
When deploying in Tomcat the web service class and data classes are the same as for standalone server, but the publishing is done via web application configuration instead of in Java code.
package server;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Data {
private int iv;
private double xv;
private String sv;
public Data() {
this(0, 0.0, "");
}
public Data(int iv, double xv, String sv) {
super();
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
}
package server;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
@Path("/data")
public class DataService {
@Context
private HttpServletResponse response;
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("/{v}")
public Data getOne(@PathParam("v") int v) {
return DataModel.getInstance().getOne(v);
}
@GET
@Produces({MediaType.APPLICATION_JSON})
@Path("")
public List<Data> getSome(@DefaultValue("-1") @QueryParam("start") int start, @DefaultValue("-1") @QueryParam("finish") int finish) {
if(start == -1 && finish == -1) {
return DataModel.getInstance().getAll();
} else {
return DataModel.getInstance().getSome(start, finish);
}
}
@POST
@Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON})
@Path("")
public Data add(Data o) {
DataModel.getInstance().add(o);
return o;
}
@PUT
@Consumes({MediaType.APPLICATION_JSON})
@Path("/{v}")
public void update(@PathParam("v") int v, Data o) {
if(v == o.getIv()) {
DataModel.getInstance().update(o);
}
}
@DELETE
@Path("/{v}")
public void remove(@PathParam("v") int v) {
DataModel.getInstance().remove(v);
}
}
package server;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class DataModel {
private static DataModel instance;
private Map<Integer,Data> data = null;
private DataModel() {
data = new TreeMap<Integer,Data>();
data.put(1, new Data(1, 1.1, "Text #1"));
data.put(2, new Data(2, 2.2, "Text #2"));
data.put(3, new Data(3, 3.3, "Text #3"));
}
public static synchronized DataModel getInstance() {
if(instance == null) {
instance = new DataModel();
}
return instance;
}
public synchronized Data getOne(int v) {
return data.get(v);
}
public synchronized List<Data> getAll() {
List<Data> res = new ArrayList<Data>();
for(Data d : data.values()) {
res.add(d);
}
return res;
}
public synchronized List<Data> getSome(int start, int finish) {
List<Data> res = new ArrayList<Data>();
for(Data d : data.values()) {
if(start <= d.getIv() & d.getIv() <= finish) {
res.add(d);
}
}
return res;
}
public synchronized void add(Data d) {
data.put(d.getIv(), d);
}
public synchronized void update(Data d) {
data.put(d.getIv(), d);
}
public synchronized void remove(int v) {
data.remove(v);
}
}
package server;
import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/api")
public class LoadServices extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> res = new HashSet<Class<?>>();
res.add(DataService.class);
return res;
}
}
WEB-INF/web.xml:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
</web-app>
The service is available at URL (if deployed in rest.war):
Directory structure on Tomcat 8.5:
Directory TOMCAT$ROOT:[webapps.rest] WEB-INF.DIR;1 Total of 1 file. Directory TOMCAT$ROOT:[webapps.rest.WEB-INF] classes.DIR;1 lib.DIR;1 web.xml;2 web.xml;1 Total of 4 files. Directory TOMCAT$ROOT:[webapps.rest.WEB-INF.classes] server.DIR;1 Total of 1 file. Directory TOMCAT$ROOT:[webapps.rest.WEB-INF.classes.server] Data.class;8 DataModel.class;8 DataService.class;8 LoadServices.class;8 ServerMain.class;8 Total of 5 files. Directory TOMCAT$ROOT:[webapps.rest.WEB-INF.lib] activation-1_1_1.jar;1 asm-3_1.jar;1 config-1_4_3.jar;1 jackson-core-asl-1_1_1.jar;1 jaxb-api-2_1.jar;1 jaxb-impl-2_1_12.jar;1 jersey-bundle-1_2.jar;1 jersey-client-1_2.jar;1 jersey-core-1_2.jar;1 jersey-json-1_2.jar;1 jersey-server-1_2.jar;1 jettison-1_1.jar;1 jsr311-api-1_1_1.jar;1 stax-api-1_0-2.jar;1 Total of 14 files. Grand total of 5 directories, 25 files.
There are several possible approaches to client:
To keep it simple we will use standard Java HttpURLConnection.
For JSON library we will use Google GSON library.
package client;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Data {
private int iv;
private double xv;
private String sv;
public Data() {
this(0, 0.0, "");
}
public Data(int iv, double xv, String sv) {
super();
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
@Override
public String toString() {
return String.format("(%d,%f,%s)", iv, xv, sv);
}
}
package client;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;
import com.google.gson.Gson;
public class ClientMain {
private static String interact(String method, String urlstr, String typ, String body) {
try {
HttpURLConnection con = (HttpURLConnection)(new URL(urlstr)).openConnection();
con.setRequestMethod(method);
con.addRequestProperty("accept", typ);
if(body != null) {
con.addRequestProperty("content-type", typ);
con.setDoOutput(true);
con.getOutputStream().write(body.getBytes());
}
StringBuilder sb = new StringBuilder();
con.connect();
if(con.getResponseCode() / 100 == 2) {
InputStream is = con.getInputStream();
byte[] b = new byte[1000];
int n;
while((n = is.read(b)) >= 0) {
sb.append(new String(b,0,n));
}
is.close();
} else {
System.out.printf("Error: %d %s\n", con.getResponseCode(), con.getResponseMessage());
}
con.disconnect();
return sb.toString();
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private static void testGetOne(String urlstr, int v) {
String response = interact("GET", urlstr + "/" + v, "application/json", null);
Gson gson = new Gson();
Data d = gson.fromJson(response, Data.class);
System.out.println(d);
}
private static void testGetAll(String urlstr) {
String response = interact("GET", urlstr, "application/json", null);
Gson gson = new Gson();
Type t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType();
Map<String,List<Data>> result = gson.fromJson(response, t);
System.out.println(result.get("data"));
}
private static void testGetSome(String urlstr, int start, int finish) {
String response = interact("GET", urlstr + "?start=" + start + "&finish=" + finish, "application/json", null);
Gson gson = new Gson();
Type t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType();
Map<String,List<Data>> result = gson.fromJson(response, t);
System.out.println(result.get("data"));
}
private static void testAdd(String urlstr, Data d1) {
Gson gson = new Gson();
String request = gson.toJson(d1);
String response = interact("POST", urlstr + "/", "application/json", request);
Data d2 = gson.fromJson(response, Data.class);
System.out.println(d2);
}
private static void testUpdate(String urlstr, int v) {
String response = interact("GET", urlstr + "/" + v, "application/json", null);
Gson gson = new Gson();
Data d = gson.fromJson(response, Data.class);
d.setXv(d.getXv() + 0.01);
d.setSv(d.getSv() + " - updated");
String request = gson.toJson(d);
interact("PUT", urlstr + "/" + v, "application/json", request);
}
private static void testRemove(String urlstr, int v) {
interact("DELETE", urlstr + "/" + v, "application/json", null);
}
private static void test(String urlstr) {
testGetOne(urlstr, 2);
testGetAll(urlstr);
testGetSome(urlstr, 2, 3);
testAdd(urlstr, new Data(4, 4.4, "Text #4"));
testGetAll(urlstr);
testGetSome(urlstr, 2, 3);
testUpdate(urlstr, 2);
testGetAll(urlstr);
testRemove(urlstr, 4);
testGetAll(urlstr);
}
public static void main(String[] args) {
test("http://localhost:8080/api/data");
//test("http://localhost:8080/rest/api/data");
}
}
$ set def [.client]
$ javac -cp .:/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar *.java
$ set def [-]
$ java -cp .:/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar "client.ClientMain"
Groovy is as always just like Java just with less code.
import com.google.gson.reflect.*
import com.google.gson.*
class Data {
int iv;
double xv;
String sv;
@Override
String toString() {
return String.format("(%d,%f,%s)", iv, xv, sv)
}
}
def interact(method, urlstr, typ, body) {
con = (new URL(urlstr)).openConnection()
con.setRequestMethod(method)
con.addRequestProperty("accept", typ)
if(body != null) {
con.addRequestProperty("content-type", typ)
con.setDoOutput(true)
con.outputStream.write(body.bytes)
}
sb = new StringBuilder()
con.connect()
if(con.responseCode.intdiv(100) == 2) {
is = con.inputStream
b = new byte[1000]
while((n = is.read(b)) >= 0) {
sb.append(new String(b, 0, n))
}
is.close()
} else {
println("Error: ${con.responseCode} ${con.responseMessage}")
}
con.disconnect()
return sb.toString()
}
def testGetOne(urlstr, v) {
response = interact("GET", urlstr + "/" + v, "application/json", null)
gson = new Gson()
d = gson.fromJson(response, Data.class)
println(d)
}
def testGetAll(urlstr) {
response = interact("GET", urlstr, "application/json", null)
gson = new Gson()
t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType()
result = gson.fromJson(response, t)
println(result.get("data"))
}
def testGetSome(urlstr, start, finish) {
response = interact("GET", urlstr + "?start=" + start + "&finish=" + finish, "application/json", null)
gson = new Gson()
t = new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType()
result = gson.fromJson(response, t)
println(result.get("data"))
}
def testAdd(urlstr, d1) {
gson = new Gson()
request = gson.toJson(d1)
response = interact("POST", urlstr + "/", "application/json", request)
d2 = gson.fromJson(response, Data.class)
println(d2)
}
def testUpdate(urlstr, v) {
response = interact("GET", urlstr + "/" + v, "application/json", null)
gson = new Gson()
d = gson.fromJson(response, Data.class)
d.xv = d.xv + 0.01
d.sv = d.sv + " - updated"
request = gson.toJson(d)
interact("PUT", urlstr + "/" + v, "application/json", request)
}
def testRemove(urlstr, v) {
interact("DELETE", urlstr + "/" + v, "application/json", null)
}
def test(urlstr) {
testGetOne(urlstr, 2)
testGetAll(urlstr)
testGetSome(urlstr, 2, 3)
testAdd(urlstr, new Data(iv: 4, xv: 4.4, sv: "Text #4"))
testGetAll(urlstr)
testGetSome(urlstr, 2, 3)
testUpdate(urlstr, 2)
testGetAll(urlstr)
testRemove(urlstr, 4)
testGetAll(urlstr)
}
test("http://localhost:8080/api/data")
//test("http://localhost:8080/rest/api/data")
$ groovy_cp = "/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar"
$ groovy client.groovy
Python is a powerful language, but in this case it require some Java glue code to make everything work.
package client;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Data {
private int iv;
private double xv;
private String sv;
public Data() {
this(0, 0.0, "");
}
public Data(int iv, double xv, String sv) {
super();
this.iv = iv;
this.xv = xv;
this.sv = sv;
}
public int getIv() {
return iv;
}
public void setIv(int iv) {
this.iv = iv;
}
public double getXv() {
return xv;
}
public void setXv(double xv) {
this.xv = xv;
}
public String getSv() {
return sv;
}
public void setSv(String sv) {
this.sv = sv;
}
@Override
public String toString() {
return String.format("(%d,%f,%s)", iv, xv, sv);
}
}
package client;
import java.util.ArrayList;
import java.util.TreeMap;
import java.lang.reflect.Type;
import com.google.gson.reflect.TypeToken;
public class DataList {
public static Type getType() {
return new TypeToken<TreeMap<String,ArrayList<Data>>>(){}.getType();
}
}
from java.net import URL
from org.python.core.util import FileUtil
from com.google.gson.reflect import TypeToken
from com.google.gson import Gson
from client import Data, DataList
def interact(method, urlstr, typ, body):
con = (URL(urlstr)).openConnection()
con.setRequestMethod(method)
con.addRequestProperty('accept', typ)
if body != None:
con.addRequestProperty('content-type', typ)
con.setDoOutput(True)
fout = FileUtil.wrap(con.outputStream)
fout.write(body)
fout.flush()
sb = ''
con.connect()
if con.responseCode // 100 == 2:
fin = FileUtil.wrap(con.inputStream)
for line in fin:
sb = sb + line
fin.close()
else:
print('Error: %d %s' % (con.responseCode, con.responseMessage))
con.disconnect()
return sb
def testGetOne(urlstr, v):
response = interact('GET', urlstr + '/' + str(v), 'application/json', None)
gson = Gson()
d = gson.fromJson(response, Data)
print(d)
def testGetAll(urlstr):
response = interact('GET', urlstr, 'application/json', None)
gson = Gson()
result = gson.fromJson(response, DataList.getType())
print(result.get('data'))
def testGetSome(urlstr, start, finish):
response = interact('GET', urlstr + '?start=' + str(start) + '&finish=' + str(finish), 'application/json', None)
gson = Gson()
result = gson.fromJson(response, DataList.getType())
print(result.get('data'))
def testAdd(urlstr, d1):
gson = Gson()
request = gson.toJson(d1)
response = interact('POST', urlstr + '/', 'application/json', request)
d2 = gson.fromJson(response, Data)
print(d2)
def testUpdate(urlstr, v):
response = interact('GET', urlstr + '/' + str(v), 'application/json', None)
gson = Gson()
d = gson.fromJson(response, Data)
d.xv = d.xv + 0.01
d.sv = d.sv + ' - updated'
request = gson.toJson(d)
interact('PUT', urlstr + '/' + str(v), 'application/json', request)
def testRemove(urlstr, v):
interact('DELETE', urlstr + '/' + str(v), 'application/json', None)
def test(urlstr):
testGetOne(urlstr, 2)
testGetAll(urlstr)
testGetSome(urlstr, 2, 3)
testAdd(urlstr, Data(4, 4.4, 'Text #4'))
testGetAll(urlstr)
testGetSome(urlstr, 2, 3)
testUpdate(urlstr, 2)
testGetAll(urlstr)
testRemove(urlstr, 4)
testGetAll(urlstr)
test('http://localhost:8080/api/data')
#test('http://localhost:8080/rest/api/data')
$ define/nolog jython_libs "/disk2/arne/jaxrs/gson-2_2_4.jar:/disk2/arne/jaxrs/jaxb-api-2_1.jar"
$ jython xclient.py
Exposing RESTful web services in Java is as easy on VMS as on any other platform.
Consuming RESTful web services in a JVM languages on VMS may require knowing a few tricks as on any other platform.
Version | Date | Description |
---|---|---|
1.0 | February 18th 2024 | Initial version |
See list of all articles here
Please send comments to Arne Vajhøj