我已经编写了两个 HATEOAS 客户端,一次用 Java,一次用 Ruby,我和你一样感到沮丧。在这两种情况下,我所做的工作都完全缺乏工具支持。例如,我使用的 REST API 会告诉我为每个超文本控件使用什么 HTTP 方法,但是HttpClient不允许您传入该方法,所以我最终得到了以下丑陋的代码(顺便说一句,所有代码都在一个自定义的 Ant 任务,因此是BuildException
s):
private HttpMethod getHypermediaControl(Node href, Node method,
NodeList children) {
if (href == null) {
return null;
}
HttpMethod control;
if (method == null || method.getNodeValue().equals("")
|| method.getNodeValue().equalsIgnoreCase("GET")) {
control = new GetMethod(href.getNodeValue());
} else if (method.getNodeValue().equalsIgnoreCase("POST")) {
control = new PostMethod(href.getNodeValue());
} else if (method.getNodeValue().equalsIgnoreCase("PUT")) {
control = new PutMethod(href.getNodeValue());
} else if (method.getNodeValue().equalsIgnoreCase("DELETE")) {
control = new DeleteMethod(href.getNodeValue());
} else {
throw new BuildException("Unknown/Unimplemented method "
+ method.getNodeValue());
}
control.addRequestHeader(accept);
return control;
}
这最终成为我使用的 REST 客户端实用程序方法的基础。
private HttpMethod getHypermediaControl(String path, Document source)
throws TransformerException, IOException {
Node node = XPathAPI.selectSingleNode(source, path);
return getHypermediaControl(node);
}
private HttpMethod getHypermediaControl(Node node) {
if (node == null) {
return null;
}
NamedNodeMap attributes = node.getAttributes();
if (attributes == null) {
return null;
}
Node href = attributes.getNamedItem("href");
Node method = attributes.getNamedItem("method");
HttpMethod control = getHypermediaControl(href, method,
node.getChildNodes());
return control;
}
private Document invokeHypermediaControl(HttpClient client, Document node,
final String path) throws TransformerException, IOException,
HttpException, URIException, SAXException,
ParserConfigurationException, FactoryConfigurationError {
HttpMethod method = getHypermediaControl(path, node);
if (method == null) {
throw new BuildException("Unable to find hypermedia controls for "
+ path);
}
int status = client.executeMethod(method);
if (status != HttpStatus.SC_OK) {
log(method.getStatusLine().toString(), Project.MSG_ERR);
log(method.getResponseBodyAsString(), Project.MSG_ERR);
throw new BuildException("Unexpected status code ("
+ method.getStatusCode() + ") from " + method.getURI());
}
String strResp = method.getResponseBodyAsString();
StringReader reader = new StringReader(strResp);
Document resp = getBuilder().parse(new InputSource(reader));
Node rval = XPathAPI.selectSingleNode(resp, "/");
if (rval == null) {
log(method.getStatusLine().toString(), Project.MSG_ERR);
log(method.getResponseBodyAsString(), Project.MSG_ERR);
throw new BuildException("Could not handle response");
}
method.releaseConnection();
return resp;
}
使用这一点代码,我可以相当轻松地编写将遍历返回文档中的超媒体控件的客户端。缺少的主要部分是对表单参数的支持。幸运的是,我使用的所有控件都是无参数的,除了一个(我在重构方面遵循三规则)。为了完整起见,代码片段如下所示:
HttpMethod licenseUpdateMethod = getHypermediaControl(
"/license/update", licenseNode);
if (licenseUpdateMethod == null) {
log(getStringFromDoc(licenseNode), Project.MSG_ERR);
throw new BuildException(
"Unable to find hypermedia controls to get the test suites or install the license");
} else if (license != null) {
EntityEnclosingMethod eem = (EntityEnclosingMethod) licenseUpdateMethod;
Part[] parts = { new StringPart("license", this.license) };
eem.setRequestEntity(new MultipartRequestEntity(parts, eem
.getParams()));
int status2 = client.executeMethod(eem);
if (status2 != HttpStatus.SC_OK) {
log(eem.getStatusLine().toString(), Project.MSG_ERR);
log(eem.getResponseBodyAsString(), Project.MSG_ERR);
throw new BuildException("Unexpected status code ("
+ eem.getStatusCode() + ") from " + eem.getURI());
}
eem.releaseConnection();
}
现在,应该做的是查看 的子级/license/update
以找出需要传递哪些参数,但这必须等到我有两个需要遵循的参数化形式。
顺便说一句,经过所有努力,在不影响客户端的情况下修改服务器非常令人满意且容易。感觉太好了,我很惊讶它在某些州没有被取缔。