I have this in my servlet:
#Override
public void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
String responseText;
try {
String base = request.getParameter("base");
String convertTo = request.getParameter("convertTo");
double amount = Double.parseDouble(request.getParameter("amount"));
responseText = currencyRatesCalculation.getConvertedAmount(base, convertTo, amount);
response.setContentType(APPLICATION_JSON);
}
catch (NullPointerException | NumberFormatException exception) {
LOG.error("{} : Exception when parsing request parameters : ", LOG_STRING, exception);
responseText = "Error when parsing request parameters";
}
response.getWriter().write(responseText);
}
This is how I'm trying to test it:
#Mock
private MockSlingHttpServletRequest req;
#Mock
private MockSlingHttpServletResponse res;
#InjectMocks
private CurrencyExchangeServlet underTest;
#Before
public void setup() {
underTest = new CurrencyExchangeServlet();
req = context.request();
res = context.response();
}
#Test
public void doGet_shouldReturnHeaderAsExpected() throws IOException {
underTest.doGet(req, res);
assertEquals(req.getContentType(), "application/json");
}
junitx.framework.ComparisonFailure:
Expected :null
Actual :application/json
Some advice? Where is mistake? Im watching on tutorials but cant find excatly some example for this what I have
Related
I have below Java code to prevent X-FRAME-OPTIONS header being set for specific product responses
public class ResponseHeaderValve extends ValveBase {
private static final Logger LOGGER = Logger.getLogger(ResponseHeaderValve.class.getName());
private static final String PRODUCT_NAME = "PRODUCT_NAME";
#Override
public void invoke(Request request, Response response) throws ServletException, IOException {
try {
HttpSession session = request.getSession();
String productName = (String) session.getAttribute(PRODUCT_NAME);
if (productName == null && request.getParameterMap().containsKey(PRODUCT_NAME)) {
productName = request.getParameter(PRODUCT_NAME);
}
if (productName != null && productName.equalsIgnoreCase("product1")) {
HttpServletResponse httpServletResponse = new HttpServletResponseWrapper(response.getResponse()) {
public void addHeader(String name, String value) {
if (!name.equalsIgnoreCase("X-FRAME-OPTIONS")) {
super.setHeader(name, value);
}
}
public void setHeader(String name, String value) {
if (!name.equalsIgnoreCase("X-FRAME-OPTIONS")) {
super.setHeader(name, value);
}
}
};
this.getNext().invoke(request,httpServletResponse);
} else {
this.getNext().invoke(request, response);
}
}
catch(Exception e){
LOGGER.log(Level.INFO, "Exception in ResponseHeaderValve: invoke():", e);
}
}
}
For cases productName equals "product1" using HttpServletResponseWrapper I tried to prevent header being set.
I have to pass request, httpServletResponse to the next valve using invoke method, but org.apache.catalina.valve invoke(Request,Response) method does not allow passing httpServletResponse. I have tried casting (Response)httpServletResponse, it did not work throws ClassCastException. How can I proceed?
I have this code, It uses the java java.net.http.HttpClient :
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class test {
private static String authorizationHeader = "Bearer ";
public static int MAX_RESEND = 5;
public static int TIME_OUT = 150000;
public static String url = "http://127.0.0.1:5000/api/test";
private static final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofMinutes(3))
.build();
public static HTTPResponse postHttpRequest(HttpClient httpClient, String uri, String body){
HttpResponse.BodyHandler<String> handler = HttpResponse.BodyHandlers.ofString();
HttpRequest request = HttpRequest.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.timeout(Duration.ofMinutes(5))
.uri(URI.create(uri))
.header("Content-Type", "application/json")
.header("Authorization", authorizationHeader)
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
CompletableFuture<HttpResponse<String>> response = httpClient.sendAsync(request, handler)
.thenComposeAsync(r -> tryResend(httpClient, request, handler, 1, r, TIME_OUT));
String getResponse = null;
Integer getResponseStatusCode = null;
try {
getResponse = response.thenApply(HttpResponse::body).get(3, TimeUnit.MINUTES);
getResponseStatusCode = response.get().statusCode();
return new HTTPResponse(getResponseStatusCode, getResponse, null) ;
} catch (Exception e) {
return new HTTPResponse(500, e.toString(), e.getMessage());
}
}
public static <T> CompletableFuture<HttpResponse<T>> tryResend(HttpClient client, HttpRequest request, HttpResponse.BodyHandler<T> handler, int count, HttpResponse<T> resp, long timeSleep) {
try {
Thread.sleep(timeSleep);
if (resp.statusCode() == 200 || count >= MAX_RESEND) {
return CompletableFuture.completedFuture(resp);
} else {
return client.sendAsync(request, handler)
.thenComposeAsync(r -> tryResend(client, request, handler, count+1, r, timeSleep));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
String body = "{\"app\":\"hi\"}";
HTTPResponse hResponseOrigin = postHttpRequest( httpClient , url , body);
Integer statusCodeOriginResponse = hResponseOrigin.getStatusCode();
String msgOriginResponse = hResponseOrigin.getResponse();
}
}
class HTTPResponse {
private Integer statusCode;
private String response;
private String exception;
public HTTPResponse(Integer statusCode, String response, String exception) {
this.statusCode = statusCode;
this.response = response;
this.exception = exception;
}
public Integer getStatusCode() {
return statusCode;
}
public void setStatusCode(Integer statusCode) {
this.statusCode = statusCode;
}
public String getResponse() {
return response;
}
public void setResponse(String response) {
this.response = response;
}
public String getException() {
return exception;
}
public void setException(String exception) {
this.exception = exception;
}
}
The code tries to do n recursive-requests when the first response of the request is not 200.
The issue always appears on the second retry.
I always get this exception:
java.util.concurrent.TimeoutException
example:
postHttpRequest ---------------------------
http://127.0.0.1:5000/api/test
tryResend ------------------------------------------------------
count zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
1
res get tryResend ----------zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzxxxxxxxxxxxxxxx
{"app":"hi"}
status code: 500
tryResend ------------------------------------------------------
count zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
2
res get tryResend ----------zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzxxxxxxxxxxxxxxx
{"app":"hi"}
msgOriginResponse xxxxxxxxxxxxxxxx----------------------------------------------------------------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
java.util.concurrent.TimeoutException
msgOriginResponse end xxxxxxxxxxxxxxxxx----------------------------------------------------------------xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
How can I fix it?? any recomendation ?
I've these two methods from my MetadataManagement class which I'd like to unit test:
#Override
protected void doPut(final HttpServletRequest request, final HttpServletResponse response,
final MetadataResource resource)
throws IOException {
ServiceCommon.checkRole(getSubject(request));
if (resource.getType() != Type.CONTAINER) {
final String err = "Request not allowed for " + request.getURI();
throw new ServiceApiException(ServiceApiError.METHOD_NOT_ALLOWED, err);
}
final String name = getContainerName(resource);
final ServiceApiMetadata config = getConfig(request, PATH);
final StorageLocation storageLocation = getStorageLocation(conf.getStorageLocation());
if (config.getNotifications() != null) {
checkMethodSupported(id);
checkService(id);
}
}
private ServiceApiMetadata getConfig(final HttpServletRequest request, final String path)
throws IOException {
final Schema schema;
try (final InputStream inStream = this.getClass().getResourceAsStream(path)) {
final JSONObject origSchema = new JSONObject(new JSONTokener(inStream));
if (isGoldStar()) {
origSchema.getJSONObject("properties")
.getJSONObject("notifications")
.getJSONObject("properties")
.getJSONObject("topic")
.put("pattern", "^[0-9A-Za-z-.]*$");
}
schema = SchemaLoader.load(origSchema);
}
final ServiceApiMetadata config;
try (final BufferedReader reader = request.getReader()) {
final JSONObject json = new JSONObject(new JSONTokener(reader));
schema.validate(json);
config = ServiceApiMetadata.read(json);
} catch (final ValidationException e) {
_logger.debug(e.getMessage());
if (e.getLocation().contains("#/properties/notifications")) {
throw new ServiceApiException(ServiceApiError.MALFORMED_NOTIFICATIONS_ERROR,
ServiceApiErrorMessage.MALFORMED_JSON);
} else {
throw new ServiceApiException(ServiceApiError.MALFORMED_JSON);
}
} catch (final JSONException e) {
_logger.debug(e.getMessage());
throw new ServiceApiException(ServiceApiError.MALFORMED_JSON);
}
return config;
}
As I understand it I can not directly call getConfig in my test because the method is private. I believe using reflection is an option but is not advised. Based on that, any test of getConfig should be done through doPut.
What I'm most interested in checking is if getConfig.isGoldStar is true, the origSchema pattern updates to ^[0-9A-Za-z]*$ and if it is false it remains at ^[0-9A-Za-z-._]*$.
To call doPut in my test I will need HttpServletRequest, HttpServletResponse and MetadataResource objects. I'm not sure how I generate these. HttpServletRequest and HttpServletResponse are from javax.servlet.ServletRequest and MetadataResource comes from within my project. It takes HttpServletRequest and an enum as parameters.
How do I do this test? I think I should be OK once I can call the doPut method but I'm struggling to do that.
EDIT: The error was in the client not the server. The response body was getting written, but the client was not reading it on a 400 response.
I have a custom message converter to produce text/csv, application/csv from an ErrorResponse object. It works as expected when the ErrorResponse is returned directly from a #RequestMapping annotated method, but returns no response body when ErrorResponse is return from an #ExceptionHandler annotated method in a #ControllerAdvice object. I have verified that the message converter writerInternal method is being called and is writing to the response body, but is never makes it back to the client.
ErrorResponse:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement(name="response")
public class ErrorResponse {
private String statusCode;
private String userMessage;
private String developerMessage;
public String getStatusCode() {
return statusCode;
}
public void setStatusCode(final String statusCode) {
this.statusCode = statusCode;
}
public String getUserMessage() {
return userMessage;
}
public void setUserMessage(final String userMessage) {
this.userMessage = userMessage;
}
public String getDeveloperMessage() {
return developerMessage;
}
public void setDeveloperMessage(final String developerMessage) {
this.developerMessage = developerMessage;
}
public ErrorResponse() {
super();
}
public ErrorResponse(final String statusCode, final String userMessage, final String developerMessage) {
super();
this.statusCode = statusCode;
this.userMessage = userMessage;
this.developerMessage = developerMessage;
}
}
MessageConverter:
public class ErrorResponseCsvMessageConverter extends AbstractHttpMessageConverter<ErrorResponse> {
public ErrorResponseCsvMessageConverter() {
super(new MediaType("application", "csv", Charset.forName("UTF-8")),
new MediaType("text", "csv", Charset.forName("UTF-8")),
MediaType.TEXT_PLAIN);
}
#Override
protected ErrorResponse readInternal(final Class<? extends ErrorResponse> clazz, final HttpInputMessage httpInputMessage)
throws IOException, HttpMessageNotReadableException {
// not supported
return null;
}
#Override
protected boolean supports(final Class<?> clazz) {
return ErrorResponse.class.isAssignableFrom(clazz);
}
#Override
protected void writeInternal(final ErrorResponse errorResponse, final HttpOutputMessage httpOutputMessage)
throws IOException, HttpMessageNotWritableException {
System.out.println(errorResponse);
try(CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(httpOutputMessage.getBody(), "UTF-8"))) {
csvWriter.writeNext(new String[] { "statusCode", "userMessage", "developerMessage" });
csvWriter.writeNext(new String[] {
errorResponse.getStatusCode(),
errorResponse.getUserMessage(),
errorResponse.getDeveloperMessage() });
}
}
}
Controller Advice:
...
#ExceptionHandler(MissingServletRequestParameterException.class)
#ResponseBody()
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleMissingParamterException(final HttpServletRequest request, final HttpServletResponse httpServletResponse, final MissingServletRequestParameterException e) {
LOG.warn("Bad Request:" +
request.getRequestURI() +
((request.getQueryString()==null) ? "" : "?" + request.getQueryString()));
return new ErrorResponse(
"400",
"There was an error with the request.",
"Required parameter '" + e.getParameterName() + "' is missing.");
}
...
I think the message is being written but not flushed...
So your converter may be missing something like:
outputMessage.getBody().flush();
Maybe even use Spring's AbstractHttpMessageConverter ?
I think my scenario is pretty common. I have a database and I want my Spring MVC app to accept a request in the controller, invoke the DB service to get data and send that data to the client as a CSV file. I'm using the JavaCSV library found here to assist in the process: http://sourceforge.net/projects/javacsv/
I've found several examples of people doing similar things and cobbled together something that looks correct-ish. When I hit the method, though, nothing is really happening.
I thought writing the data to the HttpServletResponse's outputStream would be sufficient, but apparently, I'm missing something.
Here's my controller code:
#RequestMapping(value="/getFullData.html", method = RequestMethod.GET)
public void getFullData(HttpSession session, HttpServletRequest request, HttpServletResponse response) throws IOException{
List<CompositeRequirement> allRecords = compReqServ.getFullDataSet((String)session.getAttribute("currentProject"));
response.setContentType("data:text/csv;charset=utf-8");
response.setHeader("Content-Disposition","attachment; filename=\yourData.csv\"");
OutputStream resOs= response.getOutputStream();
OutputStream buffOs= new BufferedOutputStream(resOs);
OutputStreamWriter outputwriter = new OutputStreamWriter(buffOs);
CsvWriter writer = new CsvWriter(outputwriter, '\u0009');
for(int i=1;i <allRecords.size();i++){
CompositeRequirement aReq=allRecords.get(i);
writer.write(aReq.toString());
}
outputwriter.flush();
outputwriter.close();
};
What step am I missing here? Basically, the net effect is... nothing. I would have thought setting the header and content type would cause my browser to pick up on the response and trigger a file download action.
It seems to be because your Content-type is set incorrectly, it should be response.setContentType("text/csv;charset=utf-8") instead of response.setContentType("data:text/csv;charset=utf-8").
Additionally, if you are using Spring 3, you should probably use a #ResponseBody HttpMessageConverter for code reuse. For example:
In the controller:
#RequestMapping(value = "/getFullData2.html", method = RequestMethod.GET, consumes = "text/csv")
#ResponseBody // indicate to use a compatible HttpMessageConverter
public CsvResponse getFullData(HttpSession session) throws IOException {
List<CompositeRequirement> allRecords = compReqServ.getFullDataSet((String) session.getAttribute("currentProject"));
return new CsvResponse(allRecords, "yourData.csv");
}
plus a simple HttpMessageConverter:
public class CsvMessageConverter extends AbstractHttpMessageConverter<CsvResponse> {
public static final MediaType MEDIA_TYPE = new MediaType("text", "csv", Charset.forName("utf-8"));
public CsvMessageConverter() {
super(MEDIA_TYPE);
}
protected boolean supports(Class<?> clazz) {
return CsvResponse.class.equals(clazz);
}
protected void writeInternal(CsvResponse response, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
output.getHeaders().setContentType(MEDIA_TYPE);
output.getHeaders().set("Content-Disposition", "attachment; filename=\"" + response.getFilename() + "\"");
OutputStream out = output.getBody();
CsvWriter writer = new CsvWriter(new OutputStreamWriter(out), '\u0009');
List<CompositeRequirement> allRecords = response.getRecords();
for (int i = 1; i < allRecords.size(); i++) {
CompositeRequirement aReq = allRecords.get(i);
writer.write(aReq.toString());
}
writer.close();
}
}
and a simple object to bind everything together:
public class CsvResponse {
private final String filename;
private final List<CompositeRequirement> records;
public CsvResponse(List<CompositeRequirement> records, String filename) {
this.records = records;
this.filename = filename;
}
public String getFilename() {
return filename;
}
public List<CompositeRequirement> getRecords() {
return records;
}
}
Based on Pierre answer, i did a converter. Here is the full code, that works with any Object passed:
TsvMessageConverter.java
public class TsvMessageConverter extends AbstractHttpMessageConverter<TsvResponse> {
public static final MediaType MEDIA_TYPE = new MediaType("text", "tsv", Charset.forName("utf-8"));
private static final Logger logger = LoggerFactory.getLogger(TsvMessageConverter.class);
public TsvMessageConverter() {
super(MEDIA_TYPE);
}
protected boolean supports(Class<?> clazz) {
return TsvResponse.class.equals(clazz);
}
#Override
protected TsvResponse readInternal(Class<? extends TsvResponse> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
protected void writeInternal(TsvResponse tsvResponse, HttpOutputMessage output) throws IOException, HttpMessageNotWritableException {
output.getHeaders().setContentType(MEDIA_TYPE);
output.getHeaders().set("Content-Disposition", "attachment; filename=\"" + tsvResponse.getFilename() + "\"");
final OutputStream out = output.getBody();
writeColumnTitles(tsvResponse, out);
if (tsvResponse.getRecords() != null && tsvResponse.getRecords().size() != 0) {
writeRecords(tsvResponse, out);
}
out.close();
}
private void writeRecords(TsvResponse response, OutputStream out) throws IOException {
List<String> getters = getObjectGetters(response);
for (final Object record : response.getRecords()) {
for (String getter : getters) {
try {
Method method = ReflectionUtils.findMethod(record.getClass(), getter);
out.write(method.invoke(record).toString().getBytes(Charset.forName("utf-8")));
out.write('\t');
} catch (IllegalAccessException | InvocationTargetException e) {
logger.error("Erro ao transformar em CSV", e);
}
}
out.write('\n');
}
}
private List<String> getObjectGetters(TsvResponse response) {
List<String> getters = new ArrayList<>();
for (Method method : ReflectionUtils.getAllDeclaredMethods(response.getRecords().get(0).getClass())) {
String methodName = method.getName();
if (methodName.startsWith("get") && !methodName.equals("getClass")) {
getters.add(methodName);
}
}
sort(getters);
return getters;
}
private void writeColumnTitles(TsvResponse response, OutputStream out) throws IOException {
for (String columnTitle : response.getColumnTitles()) {
out.write(columnTitle.getBytes());
out.write('\t');
}
out.write('\n');
}
}
TsvResponse.java
public class TsvResponse {
private final String filename;
private final List records;
private final String[] columnTitles;
public TsvResponse(List records, String filename, String ... columnTitles) {
this.records = records;
this.filename = filename;
this.columnTitles = columnTitles;
}
public String getFilename() {
return filename;
}
public List getRecords() {
return records;
}
public String[] getColumnTitles() {
return columnTitles;
}
}
And on SpringContext.xml add the following:
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="com.mypackage.TsvMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
So, you can use on your controller like this:
#RequestMapping(value="/tsv", method= RequestMethod.GET, produces = "text/tsv")
#ResponseBody
public TsvResponse tsv() {
return new TsvResponse(myListOfPojos, "fileName.tsv",
"Name", "Email", "Phone", "Mobile");
}