I'm trying to use REST Assured to check some properties on an HTML document returned by my server. An SSCCE demonstrating the problem would be as follows:
import static com.jayway.restassured.path.xml.config.XmlPathConfig.xmlPathConfig;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import com.jayway.restassured.path.xml.XmlPath;
public class HtmlDocumentTest {
#Test
public void titleShouldBeHelloWorld() {
final XmlPath xml = new XmlPath("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
+ "<head><title>Hello world</title></head><body></body></html>")
.using(xmlPathConfig().with().feature("http://apache.org/xml/features/disallow-doctype-decl", false));
assertThat(xml.get("//title[text()]"), is("Hello world"));
}
}
Now, this attempt ends in com.jayway.restassured.path.xml.exception.XmlPathException: Failed to parse the XML document caused by, off all the possible errors, java.net.ConnectException: Connection timed out after some 30 seconds or so!
If I remove the line with the xmlPathConfig().with().feature(...) the test fails immediately due to DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true..
If I remove the doctype line from the document the parsing succeeds but the test fails on an assertion error, "Expected: is "Hello world" but: was <Hello worldnull>" -- however, that's a different problem, obviously (but feel free to give instructions on that one, too...). And removing the doctype isn't an option for me anyway.
So, question: how do you check properties of an HTML document with a doctype using REST Assured? It says in the documentation that "REST Assured providers predefined parsers for e.g. HTML, XML and JSON.", but I cannot seem to find any examples on how exactly to activate and work with that HTML parser! There's no "HtmlPath" class like there's XmlPath, for example, and that timeout exception is very puzzling...
I checked your code. The thing is that XmlPath of Restassured isn't Xpath, but uses a property access syntax. If you add a body content to your sample HTML you will see that your XPath doesn't do much. The actual name of the query language is GPath. The following example works, note also the use of CompatibilityMode.HTML, which has the right config for you need:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import com.jayway.restassured.path.xml.XmlPath;
import com.jayway.restassured.path.xml.XmlPath.CompatibilityMode;
public class HtmlDocumentTest {
#Test
public void titleShouldBeHelloWorld() {
XmlPath doc = new XmlPath(
CompatibilityMode.HTML,
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">"
+ "<head><title>Hello world</title></head>"
+ "<body>some body"
+ "<div class=\"content\">wrapped</div>"
+ "<div class=\"content\">wrapped2</div>"
+ "</body></html>");
String title = doc.getString("html.head.title");
String content = doc.getString("html.body.div.find { it.#class == 'content' }");
String content2 = doc.getString("**.findAll { it.#class == 'content' }[1]");
assertEquals("Hello world", title);
assertEquals("wrapped", content);
assertEquals("wrapped2", content2);
}
}
If you're using the DSL (given/when/then) then XmlPath with CompatibilityMode.HTML is used automatically if the response content-type header contains a html compatible media type (such as text/html). For example if /index.html contains the following html page:
<html>
<title>My page</title>
<body>Something</body>
</html>
then you can validate the title and body like this:
when().
get("/index.html").
then().
statusCode(200).
body("html.title", equalTo("My page"),
"html.body", equalTo("Something"));
Here is sample code with the latest rest assured apis, i.e. io.restassured and not the older jayway.restassured. The explanation for the code is in the code comments.
//Demo for an api which returns a json string inside html. The json string is just an array of objects.
import io.restassured.RestAssured;
import io.restassured.path.json.JsonPath;
import io.restassured.response.Response;
import java.util.List;
import static io.restassured.RestAssured.*;
public void testMyApi() {
Response response =
when().
get("www.myapi.com/data").
then().
extract().
response();
String bodyTxt = response.htmlPath().getString("body");//Get the body element of the html response.
JsonPath jsonObj = new JsonPath(bodyTxt);//helps us to find things in a json string.
List<String> rootItems = jsonObj.getList("$");//get root element of the json part.
System.out.println(rootItems);
}
Related
i tried to find tutorials online, but i only found them in the context of web applications. in my case what i have is an html template that i want to fill "manually", meaning it should run from a static main method without spring or any other framework. just "plain thymeleaf"
i have for example a test html file containing:
<p th:text="#{test.message}">This is a report</p>
how does the java code look like that simply fills this variable? i couldn't find a minimal running code example.
The following is a standalone Thymeleaf demo (no web application needed; no Spring used):
Assumptions:
I have a messages file called test.properties containing the following:
test.message=This is a message to you.
I have a related HTML template called test.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<p th:text="#{test.message}">This is a report</p>
<p th:text="${note}">This is a note</p>
</html>
In the above template, I added Thymeleaf variable expression (${note}), as well as the message expression - just to make the demo more complete. You may not even need that.
You need to make sure that the HTML template and the properties file are in the same location, because I am using the StandardMessageResolver - see below.
I created a standalone Thymeleaf template engine class:
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.context.Context;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.messageresolver.IMessageResolver;
import org.thymeleaf.messageresolver.StandardMessageResolver;
import java.util.Map;
public class HtmlTemplateEngine {
private final TemplateEngine templateEngine;
public HtmlTemplateEngine() {
templateEngine = new org.thymeleaf.TemplateEngine();
templateEngine.addDialect(new Java8TimeDialect()); // optional extra
ClassLoaderTemplateResolver templateResolver
= new ClassLoaderTemplateResolver(Thread
.currentThread().getContextClassLoader());
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setPrefix("/thymeleaf/");
templateResolver.setCacheTTLMs(3600000L); // one hour
templateResolver.setCacheable(true);
templateResolver.setCharacterEncoding("UTF-8");
templateEngine.setTemplateResolver(templateResolver);
IMessageResolver messageResolver = new StandardMessageResolver();
templateEngine.setMessageResolver(messageResolver);
}
// use this if you do not want to pass a Context (see below). It basically does
// the same as the Context-based example, by transferring Map data to a Context:
public String getTemplate(String templateName, Map<String, Object> parameters) {
Context ctx = new Context();
if (parameters != null) {
parameters.forEach((k, v) -> {
ctx.setVariable(k, v);
});
}
return this.templateEngine.process(templateName, ctx).trim();
}
public String getTemplate(String templateName, Context ctx) {
return this.templateEngine.process(templateName, ctx).trim();
}
}
Some of the entries are default values - so they could be removed without affecting anything (for example the StandardMessageResolver will be used by default if you do not provide one).
I chose to use a ClassLoaderTemplateResolver. There are other choices you can make.
I use the above artifacts as follows:
private void processSimpleReport() {
Map<String, Object> model = new HashMap<>();
model.put("note", "my note here");
Context ctx = new Context();
//ctx.setLocale(Locale.US); // optional for i18n
ctx.setVariables(model);
HtmlTemplateEngine engine = new HtmlTemplateEngine();
String s = engine.getTemplate("test.html", ctx);
System.out.println(s);
}
Your question only uses a message - the model variables are just optional extras I added for this demo.
The resulting HTML is:
<!DOCTYPE html>
<html>
<p>This is a message to you.</p>
<p>my note here</p>
</html>
I am getting the error -
"Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0)."
when I paste the generated json from /api-docs to editor.swagger.io.
I have some observations.
localhost:8080/v3/api-docs/ yields -
"{\"openapi\":\"3.0.1\", "\info\":{ ----------------
This json gives the mentioned error in editor.swagger.io.
On manually removing the initial double quotes and escape character i.e.
{"openapi":"3.0.1", "info":{ ---------------- the error goes away i.e. UI is rendered without any issue.
My project springboot version is 2.2.13.RELEASE, springdoc-openapi-ui version is 1.5.8, jackson-databind version is 2.10.5.1
OpenAPI config class -
#Configuration
public class Config1 {
#Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("user")
.pathsToMatch("/v1/**")
.build();
}
#Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info().title("title1").description("test").version("1.0.0"));
}
Please let me know if any further information is required from my end. Any inputs regarding the issue will be helpful. Thank you.
I had the same issue with spring-boot-2.6.4, springdoc-openapi-1.6.6.
The problem is localhost:8080/v3/api-docs/ responses "{"openapi":"3.0.1", "\info":{..." which is a String, NOT a valid JSON as expected.
It turns out in my WebMvcConfig implements WebMvcConfigurer, it doesn't have a StringHttpMessageConverter, so the solution is to add it to converters:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(stringHttpMessageConverter());
...
}
#Bean
public StringHttpMessageConverter stringHttpMessageConverter() {
StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();
messageConverter.setSupportedMediaTypes(List.of(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, MediaType.ALL));
return messageConverter;
}
If you're extending WebMvcConfigurationSupport instead of implementing WebMvcConfigurer, probably you'll not get this error as the StringHttpMessageConverter is added by default.
You can use .version("v0.0.1") in your customOpenAPI or publicApi like this
#Bean
public OpenAPI springShopOpenAPI() {
return new OpenAPI()
.version("v0.0.1"));
}
For more details see docs https://springdoc.org/#migrating-from-springfox
Doing a project in .Net Core 2, using MVC, I'm attempting to incorporate a project from GitHub that generates the XML for an RSS feed. It is TAlex.RSSFeedGenerator.
To generate the actual document, the FeedGenerator's only option is serializing its RSS object to a MemoryStream:
public void Generate(Rss rss, Stream output)
{
XmlSerializer serializer = new XmlSerializer(typeof(Rss));
serializer.Serialize(output, rss);
}
So FeedGenerator has already serialized the RSS content as XML in Stream, and I want to return that content as XML from an MVC Controller ( return Ok(content_here) ).
How can I do this?
As incorporated originally / currently:
var rss = new Rss();
// ...
var RssFeedGenerator = new FeedGenerator();
var output = new MemoryStream();
RssFeedGenerator.Generate(rss, output);
output.Position = 0;
var sreader = new StreamReader(output);
return Ok(sreader.ReadToEnd());
This puts out a string and is seen as a string by the browser. It is not seen as XML or RSS.
The MVC Controller is defined and decorated like so:
[HttpGet()]
[Produces("text/xml")]
public async Task<IActionResult> GetAsync() {...}
So, I need the string that is already an XML document to be recognized as an XML document, not a string. My goal is also to send the contents as a response directly, not save it to a file and then have to redirect the user to that file or have to have Ok() read from the file.
Also, I can't send the MemoryStream directly to Ok(), because I get an error that one can't access a closed stream.
The browser is currently showing this:
<string><?xml version="1.0" encoding="utf-16"?> ... </string>
(Originally, it was properly decoding the HTML entities, so you'd see:
<string><?xml version="1.0" encoding="utf-16"?> ... </string>
but now it's not even doing that.)
Thanks for clarifying.
Please return ContentResult in order to see the RSS XML. You can have "application/xml" or "application/RSS+XML" or "text/xml"
public async Task<IActionResult> GetAsync()
{
string rssFeed = "<?xml >...";
return new ContentResult
{
Content = rssFeed,
ContentType = "application/RSS+xml"
};
}
I am using Struts2, and I want to send the html output of one of my actions as an email.
In other words, instead of displaying the output html in the browser, I want to send it as an email.
I am using Apache Tiles to create my pages.
EDIT:
In general, can we get the the html result of struts view as a stream and pass it to another action?
First of all, I need to mention that using JSPs as email templates are not a good idea. But, if you need to do this, or you need to log the exact HTML that user sees (or any other reason), you can do this:
Create an interceptor for your action
Get the response from ServletActionContext
Wrap the response
Invoke the action
get (or even change) the response.
return the result.
This is part of my code:
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
//STEP 1
public class EmailInterceptor implements Interceptor {
#Override
public String intercept(ActionInvocation actionInvocation) throws Exception {
// STEP 2
HttpServletResponse response = ServletActionContext.getResponse();
PrintWriter out = response.getWriter();
// STEP 3
CharResponseWrapper responseWrapper = new CharResponseWrapper((HttpServletResponse) response);
ServletActionContext.setResponse(responseWrapper);
// STEP 4
String result = actionInvocation.invoke();
// STEP 5
String servletResponse = new String(responseWrapper.toString());
out.write(servletResponse + "whatever");
// STEP 6
return result;
}
public class CharResponseWrapper extends HttpServletResponseWrapper {
private CharArrayWriter output;
public String toString() {
return output.toString();
}
public CharResponseWrapper(HttpServletResponse response) {
super(response);
output = new CharArrayWriter();
}
public PrintWriter getWriter() {
return new PrintWriter(output);
}
}
}
I want to embed a link in a JSF message, is this possible?
When I try it, the rendered html of the h:messages tag escapes the html characters. I tried setting the escape attribute of the h:messages tag to false, but that didn't help.
Unfortunately, this is not possible in the standard JSF implementation. The component and the renderer doesn't officially support this attribute. You can however homegrow a renderer which handles this.
Since this is a pretty common requirement/wish, I thought to take a look what's all possible.
First some background information: JSF by default uses ResponseWriter#writeText() to write the tag body, which escapes HTML by default. We'd like to let it use ResponseWriter#write() instead like as with <h:outputText escape="false" />. We'd like to extend the MessagesRenderer of the standard JSF implementation and override the encodeEnd() method accordingly. But since the MessagesRenderer#encodeEnd() contains pretty a lot of code (~180 lines) which we prefer not to copypaste to just change one or two lines after all, I found it better to replace the ResponseWriter with a custom implementation with help of ResponseWriterWrapper wherein the writeText() is been overriden to handle the escaping.
So, I ended up with this:
package com.example;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import javax.faces.render.FacesRenderer;
import com.sun.faces.renderkit.html_basic.MessagesRenderer;
#FacesRenderer(componentFamily="javax.faces.Messages", rendererType="javax.faces.Messages")
public class EscapableMessagesRenderer extends MessagesRenderer {
#Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
final ResponseWriter originalResponseWriter = context.getResponseWriter();
try {
context.setResponseWriter(new ResponseWriterWrapper() {
#Override
public ResponseWriter getWrapped() {
return originalResponseWriter;
}
#Override
public void writeText(Object text, UIComponent component, String property) throws IOException {
String string = String.valueOf(text);
String escape = (String) component.getAttributes().get("escape");
if (escape != null && !Boolean.valueOf(escape)) {
super.write(string);
} else {
super.writeText(string, component, property);
}
}
});
super.encodeEnd(context, component); // Now, render it!
} finally {
context.setResponseWriter(originalResponseWriter); // Restore original writer.
}
}
}
In spite of the #FacesRenderer annotation, it get overriden by the default MessagesRenderer implementation. I suspect here a bug, so I reported issue 1748. To get it to work anyway, we have to fall back to the faces-config.xml:
<render-kit>
<renderer>
<component-family>javax.faces.Messages</component-family>
<renderer-type>javax.faces.Messages</renderer-type>
<renderer-class>com.example.EscapableMessagesRenderer</renderer-class>
</renderer>
</render-kit>
Then, to trigger it, just do:
<h:messages escape="false" />
And it works! :)
Note: the above affects <h:messages> only. To do the same for <h:message>, just do the same, but replace anywhere "Messages" by "Message" (component family, renderer type and classnames).
The escape="false" attributed you need is provided by the OmniFaces <o:messages> component. The OmniFaces utility library is available for JSF 2.
I posted this solution mentioned by #BalusC's comment as an answer since this is the most straightforward solution.