GWT RPC Testing DB connection - database

I'm using GWT RPC to connect to server side. On server side I've to connect to MySQL database, from which I get data I need. My question is which is best practice when I want to test my application, because I haven't deployed it yet on Tomcat app server, and it seems I cant test it, if it is not deployed. When I test it in development mode, I can't connect to database. Do I have to deploy it on Tomcat to test my application, or is there any other way.
P.S. I included JDBC driver, so that is not a problem.

You don't need to deploy to Tomcat or any other webserver to be able to test your application. Separate you test in several layers (JDBC or JPA layer, Service layer), use JUnit and Spring test and you'll be fine.
Take a look at this documentation:
http://static.springsource.org/spring/docs/2.5.x/reference/testing.html

Don't really know what problems you're encountering at this time since you don't have any exceptions posted, but the best practice I follow when I test my GWT code in Development Mode is to proxy the request from GWT to a backend implementation running on a different app server. Since you're planning on moving your code to tomcat, I'm assuming you will be moving your GWT compiled code under the war directory of your J2EE project running on tomcat and the actual db calls will be done from this J2EE project. Follow the below steps.
Basically, in your GWT application, on the 'server' side, you need to create a proxy servlet that will proxy all of the requests to a different port (the port on which your backend application is running -- for example tomcat's port).
You will need to have both applications running at the same time (obviously on different ports). That way, when you're in GWT Development Mode, all of the requests are sent to the jetty app server first, and then forwarded to tomcat, and that's how you can keep on testing without copying the files back and forth and testing your real backend implementation.
The proxy Servlet (from http://edwardstx.net/wiki/attach/HttpProxyServlet/ProxyServlet.java)
package com.xxxxxxx.xxxxx.gwt.xxxxx.server;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
/**
* ProxyServlet from http://edwardstx.net/wiki/attach/HttpProxyServlet/ProxyServlet.java
* (This seems to be a derivative of Noodle -- http://noodle.tigris.org/)
*
* Patched to skip "Transfer-Encoding: chunked" headers, avoid double slashes
* in proxied URLs, handle GZip and allow GWT RPC.
*/
public class ProxyServlet extends HttpServlet {
private static final int FOUR_KB = 4196;
/**
* Serialization UID.
*/
private static final long serialVersionUID = 1L;
/**
* Key for redirect location header.
*/
private static final String STRING_LOCATION_HEADER = "Location";
/**
* Key for content type header.
*/
private static final String STRING_CONTENT_TYPE_HEADER_NAME = "Content-Type";
/**
* Key for content length header.
*/
private static final String STRING_CONTENT_LENGTH_HEADER_NAME = "Content-Length";
/**
* Key for host header
*/
private static final String STRING_HOST_HEADER_NAME = "Host";
/**
* The directory to use to temporarily store uploaded files
*/
private static final File FILE_UPLOAD_TEMP_DIRECTORY = new File(System.getProperty("java.io.tmpdir"));
// Proxy host params
/**
* The host to which we are proxying requests. Default value is "localhost".
*/
private String stringProxyHost = "localhost";
/**
* The port on the proxy host to wihch we are proxying requests. Default value is 80.
*/
private int intProxyPort = 80;
/**
* The (optional) path on the proxy host to wihch we are proxying requests. Default value is "".
*/
private String stringProxyPath = "";
/**
* Setting that allows removing the initial path from client. Allows specifying /twitter/* as synonym for twitter.com.
*/
private boolean removePrefix;
/**
* The maximum size for uploaded files in bytes. Default value is 5MB.
*/
private int intMaxFileUploadSize = 5 * 1024 * 1024;
private boolean isSecure;
private boolean followRedirects;
/**
* Initialize the ProxyServlet
* #param servletConfig The Servlet configuration passed in by the servlet container
*/
public void init(ServletConfig servletConfig) {
// Get the proxy host
String stringProxyHostNew = servletConfig.getInitParameter("proxyHost");
if (stringProxyHostNew == null || stringProxyHostNew.length() == 0) {
throw new IllegalArgumentException("Proxy host not set, please set init-param 'proxyHost' in web.xml");
}
this.setProxyHost(stringProxyHostNew);
// Get the proxy port if specified
String stringProxyPortNew = servletConfig.getInitParameter("proxyPort");
if (stringProxyPortNew != null && stringProxyPortNew.length() > 0) {
this.setProxyPort(Integer.parseInt(stringProxyPortNew));
}
// Get the proxy path if specified
String stringProxyPathNew = servletConfig.getInitParameter("proxyPath");
if (stringProxyPathNew != null && stringProxyPathNew.length() > 0) {
this.setProxyPath(stringProxyPathNew);
}
// Get the maximum file upload size if specified
String stringMaxFileUploadSize = servletConfig.getInitParameter("maxFileUploadSize");
if (stringMaxFileUploadSize != null && stringMaxFileUploadSize.length() > 0) {
this.setMaxFileUploadSize(Integer.parseInt(stringMaxFileUploadSize));
}
}
/**
* Performs an HTTP GET request
* #param httpServletRequest The {#link HttpServletRequest} object passed
* in by the servlet engine representing the
* client request to be proxied
* #param httpServletResponse The {#link HttpServletResponse} object by which
* we can send a proxied response to the client
*/
public void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws IOException, ServletException {
// Create a GET request
String destinationUrl = this.getProxyURL(httpServletRequest);
debug("GET Request URL: " + httpServletRequest.getRequestURL(),
"Destination URL: " + destinationUrl);
GetMethod getMethodProxyRequest = new GetMethod(destinationUrl);
// Forward the request headers
setProxyRequestHeaders(httpServletRequest, getMethodProxyRequest);
setProxyRequestCookies(httpServletRequest, getMethodProxyRequest);
// Execute the proxy request
this.executeProxyRequest(getMethodProxyRequest, httpServletRequest, httpServletResponse);
}
/**
* Performs an HTTP POST request
* #param httpServletRequest The {#link HttpServletRequest} object passed
* in by the servlet engine representing the
* client request to be proxied
* #param httpServletResponse The {#link HttpServletResponse} object by which
* we can send a proxied response to the client
*/
public void doPost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws IOException, ServletException {
// Create a standard POST request
String contentType = httpServletRequest.getContentType();
String destinationUrl = this.getProxyURL(httpServletRequest);
debug("POST Request URL: " + httpServletRequest.getRequestURL(),
" Content Type: " + contentType,
" Destination URL: " + destinationUrl);
PostMethod postMethodProxyRequest = new PostMethod(destinationUrl);
// Forward the request headers
setProxyRequestHeaders(httpServletRequest, postMethodProxyRequest);
setProxyRequestCookies(httpServletRequest, postMethodProxyRequest);
// Check if this is a mulitpart (file upload) POST
if (contentType == null || PostMethod.FORM_URL_ENCODED_CONTENT_TYPE.equals(contentType)) {
this.handleStandardPost(postMethodProxyRequest, httpServletRequest);
} else {
this.handleContentPost(postMethodProxyRequest, httpServletRequest);
}
// Execute the proxy request
this.executeProxyRequest(postMethodProxyRequest, httpServletRequest, httpServletResponse);
}
/**
* Sets up the given {#link PostMethod} to send the same standard POST
* data as was sent in the given {#link HttpServletRequest}
* #param postMethodProxyRequest The {#link PostMethod} that we are
* configuring to send a standard POST request
* #param httpServletRequest The {#link HttpServletRequest} that contains
* the POST data to be sent via the {#link PostMethod}
*/
#SuppressWarnings("unchecked")
private void handleStandardPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest) {
// Get the client POST data as a Map
Map mapPostParameters = (Map) httpServletRequest.getParameterMap();
// Create a List to hold the NameValuePairs to be passed to the PostMethod
List listNameValuePairs = new ArrayList();
// Iterate the parameter names
for (String stringParameterName : mapPostParameters.keySet()) {
// Iterate the values for each parameter name
String[] stringArrayParameterValues = mapPostParameters.get(stringParameterName);
for (String stringParamterValue : stringArrayParameterValues) {
// Create a NameValuePair and store in list
NameValuePair nameValuePair = new NameValuePair(stringParameterName, stringParamterValue);
listNameValuePairs.add(nameValuePair);
}
}
// Set the proxy request POST data
postMethodProxyRequest.setRequestBody(listNameValuePairs.toArray(new NameValuePair[]{}));
}
/**
* Sets up the given {#link PostMethod} to send the same content POST
* data (JSON, XML, etc.) as was sent in the given {#link HttpServletRequest}
* #param postMethodProxyRequest The {#link PostMethod} that we are
* configuring to send a standard POST request
* #param httpServletRequest The {#link HttpServletRequest} that contains
* the POST data to be sent via the {#link PostMethod}
*/
private void handleContentPost(PostMethod postMethodProxyRequest, HttpServletRequest httpServletRequest) throws IOException, ServletException {
StringBuilder content = new StringBuilder();
BufferedReader reader = httpServletRequest.getReader();
for (;;) {
String line = reader.readLine();
if (line == null) break;
content.append(line);
}
String contentType = httpServletRequest.getContentType();
String postContent = content.toString();
if (contentType.startsWith("text/x-gwt-rpc")) {
String clientHost = httpServletRequest.getLocalName();
if (clientHost.equals("127.0.0.1")) {
clientHost = "localhost";
}
int clientPort = httpServletRequest.getLocalPort();
String clientUrl = clientHost + ((clientPort != 80) ? ":" + clientPort : "");
String serverUrl = stringProxyHost + ((intProxyPort != 80) ? ":" + intProxyPort : "") + httpServletRequest.getServletPath();
//debug("Replacing client (" + clientUrl + ") with server (" + serverUrl + ")");
postContent = postContent.replace(clientUrl , serverUrl);
}
String encoding = httpServletRequest.getCharacterEncoding();
debug("POST Content Type: " + contentType + " Encoding: " + encoding,
"Content: " + postContent);
StringRequestEntity entity;
try {
entity = new StringRequestEntity(postContent, contentType, encoding);
} catch (UnsupportedEncodingException e) {
throw new ServletException(e);
}
// Set the proxy request POST data
postMethodProxyRequest.setRequestEntity(entity);
}
/**
* Executes the {#link HttpMethod} passed in and sends the proxy response
* back to the client via the given {#link HttpServletResponse}
* #param httpMethodProxyRequest An object representing the proxy request to be made
* #param httpServletResponse An object by which we can send the proxied
* response back to the client
* #throws IOException Can be thrown by the {#link HttpClient}.executeMethod
* #throws ServletException Can be thrown to indicate that another error has occurred
*/
private void executeProxyRequest(
HttpMethod httpMethodProxyRequest,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException, ServletException {
// Create a default HttpClient
HttpClient httpClient = new HttpClient();
httpMethodProxyRequest.setFollowRedirects(false);
// Execute the request
int intProxyResponseCode = httpClient.executeMethod(httpMethodProxyRequest);
String response = httpMethodProxyRequest.getResponseBodyAsString();
// Check if the proxy response is a redirect
// The following code is adapted from org.tigris.noodle.filters.CheckForRedirect
// Hooray for open source software
if (intProxyResponseCode >= HttpServletResponse.SC_MULTIPLE_CHOICES /* 300 */ && intProxyResponseCode responseHeaders = Arrays.asList(headerArrayResponse);
if (isBodyParameterGzipped(responseHeaders)) {
debug("GZipped: true");
if (!followRedirects && intProxyResponseCode == HttpServletResponse.SC_MOVED_TEMPORARILY) {
response = httpMethodProxyRequest.getResponseHeader(STRING_LOCATION_HEADER).getValue();
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
intProxyResponseCode = HttpServletResponse.SC_OK;
httpServletResponse.setHeader(STRING_LOCATION_HEADER, response);
} else {
response = new String(ungzip(httpMethodProxyRequest.getResponseBody()));
}
httpServletResponse.setContentLength(response.length());
}
// Send the content to the client
debug("Received status code: " + intProxyResponseCode,
"Response: " + response);
httpServletResponse.getWriter().write(response);
}
/**
* The response body will be assumed to be gzipped if the GZIP header has been set.
*
* #param responseHeaders of response headers
* #return true if the body is gzipped
*/
private boolean isBodyParameterGzipped(List responseHeaders) {
for (Header header : responseHeaders) {
if (header.getValue().equals("gzip")) {
return true;
}
}
return false;
}
/**
* A highly performant ungzip implementation. Do not refactor this without taking new timings.
* See ElementTest in ehcache for timings
*
* #param gzipped the gzipped content
* #return an ungzipped byte[]
* #throws java.io.IOException when something bad happens
*/
private byte[] ungzip(final byte[] gzipped) throws IOException {
final GZIPInputStream inputStream = new GZIPInputStream(new ByteArrayInputStream(gzipped));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(gzipped.length);
final byte[] buffer = new byte[FOUR_KB];
int bytesRead = 0;
while (bytesRead != -1) {
bytesRead = inputStream.read(buffer, 0, FOUR_KB);
if (bytesRead != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
}
byte[] ungzipped = byteArrayOutputStream.toByteArray();
inputStream.close();
byteArrayOutputStream.close();
return ungzipped;
}
public String getServletInfo() {
return "GWT Proxy Servlet";
}
/**
* Retrieves all of the headers from the servlet request and sets them on
* the proxy request
*
* #param httpServletRequest The request object representing the client's
* request to the servlet engine
* #param httpMethodProxyRequest The request that we are about to send to
* the proxy host
*/
#SuppressWarnings("unchecked")
private void setProxyRequestHeaders(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
// Get an Enumeration of all of the header names sent by the client
Enumeration enumerationOfHeaderNames = httpServletRequest.getHeaderNames();
while (enumerationOfHeaderNames.hasMoreElements()) {
String stringHeaderName = (String) enumerationOfHeaderNames.nextElement();
if (stringHeaderName.equalsIgnoreCase(STRING_CONTENT_LENGTH_HEADER_NAME)) {
continue;
}
// As per the Java Servlet API 2.5 documentation:
// Some headers, such as Accept-Language can be sent by clients
// as several headers each with a different value rather than
// sending the header as a comma separated list.
// Thus, we get an Enumeration of the header values sent by the client
Enumeration enumerationOfHeaderValues = httpServletRequest.getHeaders(stringHeaderName);
while (enumerationOfHeaderValues.hasMoreElements()) {
String stringHeaderValue = (String) enumerationOfHeaderValues.nextElement();
// In case the proxy host is running multiple virtual servers,
// rewrite the Host header to ensure that we get content from
// the correct virtual server
if (stringHeaderName.equalsIgnoreCase(STRING_HOST_HEADER_NAME)) {
stringHeaderValue = getProxyHostAndPort();
}
Header header = new Header(stringHeaderName, stringHeaderValue);
// Set the same header on the proxy request
httpMethodProxyRequest.setRequestHeader(header);
}
}
}
/**
* Retrieves all of the cookies from the servlet request and sets them on
* the proxy request
*
* #param httpServletRequest The request object representing the client's
* request to the servlet engine
* #param httpMethodProxyRequest The request that we are about to send to
* the proxy host
*/
#SuppressWarnings("unchecked")
private void setProxyRequestCookies(HttpServletRequest httpServletRequest, HttpMethod httpMethodProxyRequest) {
// Get an array of all of all the cookies sent by the client
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies == null) {
return;
}
for (Cookie cookie : cookies) {
cookie.setDomain(stringProxyHost);
cookie.setPath(httpServletRequest.getServletPath());
httpMethodProxyRequest.setRequestHeader("Cookie", cookie.getName() + "=" + cookie.getValue() + "; Path=" + cookie.getPath());
}
}
// Accessors
private String getProxyURL(HttpServletRequest httpServletRequest) {
// Set the protocol to HTTP
String protocol = (isSecure) ? "https://" : "http://";
String stringProxyURL = protocol + this.getProxyHostAndPort() + "/gui";
// simply use whatever servlet path that was part of the request as opposed to getting a preset/configurable proxy path
if (!removePrefix) {
if (httpServletRequest.getServletPath() != null) {
stringProxyURL += httpServletRequest.getServletPath();
}
}
stringProxyURL += "/";
// Handle the path given to the servlet
String pathInfo = httpServletRequest.getPathInfo();
if (pathInfo != null && pathInfo.startsWith("/")) {
if (stringProxyURL != null && stringProxyURL.endsWith("/")) {
// avoid double '/'
stringProxyURL += pathInfo.substring(1);
}
} else {
stringProxyURL += httpServletRequest.getPathInfo();
}
// Handle the query string
if (httpServletRequest.getQueryString() != null) {
stringProxyURL += "?" + httpServletRequest.getQueryString();
}
stringProxyURL = stringProxyURL.replaceAll("/null", "");
//System.out.println("----stringProxyURL: " + stringProxyURL);
return stringProxyURL;
}
private String getProxyHostAndPort() {
if (this.getProxyPort() == 80) {
return this.getProxyHost();
} else {
return this.getProxyHost() + ":" + this.getProxyPort();
}
}
protected String getProxyHost() {
return this.stringProxyHost;
}
protected void setProxyHost(String stringProxyHostNew) {
this.stringProxyHost = stringProxyHostNew;
}
protected int getProxyPort() {
return this.intProxyPort;
}
protected void setSecure(boolean secure) {
this.isSecure = secure;
}
protected void setFollowRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
}
protected void setProxyPort(int intProxyPortNew) {
this.intProxyPort = intProxyPortNew;
}
protected String getProxyPath() {
return this.stringProxyPath;
}
protected void setProxyPath(String stringProxyPathNew) {
this.stringProxyPath = stringProxyPathNew;
}
protected void setRemovePrefix(boolean removePrefix) {
this.removePrefix = removePrefix;
}
protected int getMaxFileUploadSize() {
return this.intMaxFileUploadSize;
}
protected void setMaxFileUploadSize(int intMaxFileUploadSizeNew) {
this.intMaxFileUploadSize = intMaxFileUploadSizeNew;
}
private void debug(String ... msg) {
for (String m : msg) {
//System.out.println("[DEBUG] " + m);
}
}
}
You then need to subclass it and provide the port:
package xxx.xxxx.xxxxx;
import javax.servlet.ServletConfig;
public class MyProxyServlet extends ProxyServlet {
public void init(ServletConfig servletConfig) {
//System.out.println("in the init");
setFollowRedirects(true);
setRemovePrefix(false);
//setProxyPath("gui/" + getProxyPath());
setProxyPort(8080);
}
}

Related

Alternative way to show upload percentage

Because of the bug https://github.com/codenameone/CodenameOne/issues/3043, I don't know how to show upload percentage when using a MultipartRequest. Do you have any suggestion, like an alternative way to show the percentage? Thank you
I solved this issue, workarounding it. After spending some days trying client-only solutions, finally I ended up in a solution that involves client code (Codename One) and server code (Spring Boot).
Basically, on the client I split the file to be upload in small pieces of 100kb and I upload them one by one, so I can calculate exactly the uploaded percentage. On the server, I put a controller to receive the small files and another controller to merge them. I know that my code is specific for my use case (sending images and videos to Cloudinary), however I copy some relevant parts that could inspire other people that have a similar problem with Codename One.
Screenshot ("Caricamento" means "Uploading" and "Annulla" means "Cancel"):
Client code
Server class
/**
* SYNC - Upload a MultipartFile as partial file
*
* #param data
* #param partNumber
* #param uniqueId containing the totalBytes before the first "-"
* #return true if success, false otherwise
*/
public static boolean uploadPartialFile(byte[] data, int partNumber, String uniqueId) {
String api = "/cloud/partialUpload";
MultipartRequest request = new MultipartRequest();
request.setUrl(Server.getServerURL() + api);
request.addData("file", data, "application/octet-stream");
request.addRequestHeader("authToken", DB.userDB.authToken.get());
request.addRequestHeader("email", DB.userDB.email.get());
request.addRequestHeader("partNumber", partNumber + "");
request.addRequestHeader("uniqueId", uniqueId);
NetworkManager.getInstance().addToQueueAndWait(request);
try {
String response = Util.readToString(new ByteArrayInputStream(request.getResponseData()), "UTF-8");
if ("OK".equals(response)) {
return true;
}
} catch (IOException ex) {
Log.p("Server.uploadPartialFile ERROR -> Util.readToString failed");
Log.e(ex);
SendLog.sendLogAsync();
}
return false;
}
/**
* ASYNC - Merges the previously upload partial files
*
* #param uniqueId containing the totalBytes before the first "-"
* #param callback to do something with the publicId of the uploaded file
*/
public static void mergeUpload(String uniqueId, OnComplete<Response<String>> callback) {
String api = "/cloud/mergeUpload";
Map<String, String> headers = Server.getUserHeaders();
headers.put("uniqueId", uniqueId);
Server.asyncGET(api, headers, callback);
}
public static void uploadFile(String filePath, OnComplete<String> callback) {
String api = "/cloud/upload";
// to show the progress, we send a piece of the file at a time
String url = Server.getServerURL() + api;
Map<String, String> headers = new HashMap<>();
headers.put("authToken", DB.userDB.authToken.get());
headers.put("email", DB.userDB.email.get());
DialogUtilities.genericUploadProgress(url, filePath, headers, callback);
}
}
DialogUtilities class
public static void genericUploadProgress(String url, String filePath, Map<String, String> headers, OnComplete<String> callback) {
Command[] cmds = {Command.create("Cancel", null, ev -> {
((Dialog) Display.getInstance().getCurrent()).dispose();
uploadThread.kill();
})};
Container bodyCmp = new Container(new BorderLayout());
Label infoText = new Label("DialogUtilities-Upload-Starting");
bodyCmp.add(BorderLayout.CENTER, infoText);
// Dialog blocks the current thread (that is the EDT), so the following code needs to be run in another thread
uploadThread.run(() -> {
// waits some time to give the Dialog the time to be open
// it's not necessary, but useful to use the SelectorUtilities below in the case that the uploaded file is very small
Util.sleep(500);
try {
long size = FileSystemStorage.getInstance().getLength(filePath);
String uniqueId = size + "-" + DB.userDB.email + "_" + System.currentTimeMillis();
// splits the file in blocks of 100kb
InputStream inputStream = FileSystemStorage.getInstance().openInputStream(filePath);
byte[] buffer = new byte[100 * 1024];
int readByte = inputStream.read(buffer);
int totalReadByte = 0;
int partNumber = 0;
while (readByte != -1) {
boolean result = Server.uploadPartialFile(Arrays.copyOfRange(buffer, 0, readByte), partNumber, uniqueId);
if (!result) {
CN.callSerially(() -> {
DialogUtilities.genericServerError();
});
break;
}
partNumber++;
totalReadByte += readByte;
int percentage = (int) (totalReadByte * 100 / size);
CN.callSerially(() -> {
infoText.setText(percentage + "%");
});
readByte = inputStream.read(buffer);
}
CN.callSerially(() -> {
if (CN.getCurrentForm() instanceof Dialog) {
// upload finished, before merging the files on the server we disable the "Cancel" button
Button cancelBtn = SelectorUtilities.$(Button.class, CN.getCurrentForm()).iterator().next();
cancelBtn.setEnabled(false);
cancelBtn.setText("DialogUtilities-Wait");
cancelBtn.repaint();
}
});
Server.mergeUpload(uniqueId, new OnComplete<Response<String>>() {
#Override
public void completed(Response<String> response) {
String fileId = response.getResponseData();
CN.callSerially(() -> {
if (Display.getInstance().getCurrent() instanceof Dialog) {
((Dialog) Display.getInstance().getCurrent()).dispose();
}
});
callback.completed(fileId);
}
});
} catch (IOException ex) {
Log.p("DialogUtilities.genericUploadProgress ERROR", Log.ERROR);
CN.callSerially(() -> {
DialogUtilities.genericDialogError("DialogUtilities-UploadError-Title", "DialogUtilities-UploadError-Text");
});
Log.e(ex);
SendLog.sendLogAsync();
}
});
showDialog("Server-Uploading", null, cmds[0], cmds, DialogUtilities.TYPE_UPLOAD, null, 0l, CommonTransitions.createDialogPulsate().copy(false), null, null, bodyCmp);
Server code
CloudinaryController class
/**
* Upload a MultipartFile as partial file.
*
* #param authToken
* #param email
* #param partNumber
* #param uniqueId containing the totalBytes before the first "-"
* #param file
* #return "OK" if success
*/
#PostMapping("/partialUpload")
public #ResponseBody
String partialUpload(#RequestHeader(value = "authToken") String authToken, #RequestHeader(value = "email") String email, #RequestHeader(value = "partNumber") String partNumber, #RequestHeader(value = "uniqueId") String uniqueId, #RequestParam("file") MultipartFile file) throws IOException {
return cloudinaryService.partialUpload(authToken, email, partNumber, uniqueId, file);
}
/**
* Merges the files previuosly uploaded by "/partialUpload", upload that
* file to Cloudinary and returns the id assigned by Cloudinary
*
* #param authToken
* #param email
* #param uniqueId containing the totalBytes before the first "-"
* #return the id assigned by Cloudinary
*/
#GetMapping("/mergeUpload")
public #ResponseBody
String mergeUpload(#RequestHeader(value = "authToken") String authToken, #RequestHeader(value = "email") String email, #RequestHeader(value = "uniqueId") String uniqueId) throws IOException {
return cloudinaryService.mergeUpload(authToken, email, uniqueId);
}
CloudinaryService class
/**
* Upload a MultipartFile as partial file.
*
* #param authToken
* #param email
* #param partNumber
* #param uniqueId containing the totalBytes before the first "-"
* #param file
* #return "OK" if success
*/
public String partialUpload(String authToken, String email, String partNumber, String uniqueId, MultipartFile file) throws IOException {
User user = userService.getUser(authToken, email);
if (user != null) {
String output = AppApplication.uploadTempDir + "/" + uniqueId + "-" + partNumber;
Path destination = Paths.get(output);
Files.copy(file.getInputStream(), destination, StandardCopyOption.REPLACE_EXISTING);
return "OK";
} else {
logger.error("Error: a not authenticated user tried to upload a file (email: " + email + ", authToken: " + authToken + ")");
return null;
}
}
/**
* Merges the files previuosly uploaded by "/partialUpload", upload that
* file to Cloudinary and returns the id assigned by Cloudinary
*
* #param authToken
* #param email
* #param uniqueId containing the totalBytes before the first "-"
* #return the id assigned by Cloudinary
*/
public String mergeUpload(String authToken, String email, String uniqueId) throws IOException {
User user = userService.getUser(authToken, email);
if (user != null) {
long totalBytes = Long.valueOf(uniqueId.split("-", 2)[0]);
List<File> files = new ArrayList<>();
int partNumber = 0;
File testFile = new File(AppApplication.uploadTempDir + "/" + uniqueId + "-" + partNumber);
while (testFile.exists()) {
files.add(testFile);
partNumber++;
testFile = new File(AppApplication.uploadTempDir + "/" + uniqueId + "-" + partNumber);
}
// the list of files is ready, we can now merge them
File merged = new File(AppApplication.uploadTempDir + "/" + uniqueId);
IOCopier.joinFiles(merged, files);
// uploads the file to Cloudinary
Map uploadResult = cloudinary.uploader().upload(merged, ObjectUtils.emptyMap());
String publicId = uploadResult.get("public_id").toString();
// removes the files
for (File file : files) {
file.delete();
}
merged.delete();
return publicId;
} else {
logger.error("Error: a not authenticated user tried to upload a file (email: " + email + ", authToken: " + authToken + ")");
return null;
}
}
IOCopier class
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.apache.commons.io.IOUtils;
/**
* Useful to merge files. See: https://stackoverflow.com/a/14673198
*/
public class IOCopier {
public static void joinFiles(File destination, List<File> sources)
throws IOException {
OutputStream output = null;
try {
output = createAppendableStream(destination);
for (File source : sources) {
appendFile(output, source);
}
} finally {
IOUtils.closeQuietly(output);
}
}
private static BufferedOutputStream createAppendableStream(File destination)
throws FileNotFoundException {
return new BufferedOutputStream(new FileOutputStream(destination, true));
}
private static void appendFile(OutputStream output, File source)
throws IOException {
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(source));
IOUtils.copy(input, output);
} finally {
IOUtils.closeQuietly(input);
}
}
}
Not at this time as there's no evaluation for where the issue lies. I think the progress listener tracks the output stream writing to the upload code not the actual connection time which is normally hard to track in Java anyway.
E.g. in Java SE you would open a URL and then write to the output stream of a POST connection. Then the writing would actually occur when you try to get the input stream response. But at this point I would have no indication about the state of the upload as it's completely abstracted and happening under the hood.
So I'm not sure if this is even technically feasible.

Spring 4 Security + CORS + AngularJS - Weird redirect

I am having issues with my Spring backend and an AngularJS frontend. As an info, I'm pretty new to Spring Security and learning with this project as well.
I'm not using SpringBoot. Both work seperately and are supposed to be able to run on seperate machines. ATM my frontend is running locally via gulp server on https://localhost:3000, the backend is running in a Tomcat at https://localhost:8443/context. I've set up a CORSFilter in Java.
So far, so good. If I start the frontend, calls are being made to the backend for getting resources and I'm seing the login page. If I choose login, the call is being made to https://localhost:8443/context/login, as its supposed to. But: After the login is being processed in the backend, the backend does a redirect to https://localhost:8443/context instead of https://localhost:3000, which of course creates a 404 and results in a failed login (frontend-wise). I just can't find where this weird redirect is being made.
SpringSecurityConfig:
private static final String C440_LOGIN = "/login";
private static final String c440_START_PAGE = "/index.html";
private static final String FAVICON_ICO = "/favicon.ico";
#Override
protected void configure(HttpSecurity http) throws Exception {
// HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), SessionManagementFilter.class); does not work!
HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
workHttp.addFilterBefore(new CookieFilter(), ChannelProcessingFilter.class);
workHttp.addFilterBefore(getUsernamePasswordPortalAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// set authorizations
workHttp = authorizeRequests(http);
// login handling
workHttp = formLogin(workHttp);
// exception handling
workHttp = exceptionHandling(workHttp);
// logout handling
workHttp = logout(workHttp);
// cookie handling
workHttp = rememberMe(workHttp);
// disable caching because if IE11 webfonds bug
// https://stackoverflow.com/questions/7748140/font-face-eot-not-loading-over-https
http.headers().cacheControl().disable();
csrf(workHttp);
}
/**
* Configures request authorization.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity authorizeRequests(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
// secured pages
.antMatchers("/", getCustomerdWebRessourceSecuredPath()).authenticated()
// common resources
.antMatchers("/app/**").permitAll()
.antMatchers("/profiles/**").permitAll()
.antMatchers("/captcha/**").permitAll()
.antMatchers("/", getCustomerRessourcePath()).permitAll()
.antMatchers("/", getCustomerWebRessourcePath()).permitAll()
.antMatchers("/", c440_START_PAGE).permitAll()
.antMatchers("/", FAVICON_ICO).permitAll()
.antMatchers(C440_LOGIN).permitAll()
// frontend services
.antMatchers("/services/userService/**").permitAll()
.antMatchers("/services/applicationService/**").permitAll()
.antMatchers("/services/textContentService/**").permitAll()
.antMatchers("/services/textContentBlockService/**").permitAll()
.antMatchers("/services/menuItemService/**").permitAll()
.antMatchers("/services/calculatorService/**").permitAll()
.anyRequest().authenticated()
.and();
}
private String getCustomerRessourcePath() {
return "/resources/app-" + portalFrontendBase + "/**";
}
private String getCustomerWebRessourcePath() {
return "/app-" + portalFrontendBase + "/**";
}
private String getCustomerdWebRessourceSecuredPath() {
return "/app-" + portalFrontendBase + "/secure/**";
}
/**
* Configures form login.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity exceptionHandling(HttpSecurity http) throws Exception {
return http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> {
if (authException != null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
/**
* IMPORTANT: do not redirect the requests. The front-end will be responsible to do this.
* Otherwise the unauthorized status cannot be caught in the front-end correctly.
*/
return;
}
})
.and();
}
/**
* Configures form login.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity formLogin(HttpSecurity http) throws Exception {
return http
.formLogin()
.loginPage(c440_START_PAGE)
.successHandler(getAuthenticationSuccessHandler())
.failureHandler(getAuthenticationFailureHandler())
.loginProcessingUrl(C440_LOGIN)
.permitAll()
.and();
}
/**
* Configures logout.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity logout(HttpSecurity http) throws Exception {
return http
.logout()
.logoutUrl(portalLogoutURL)
.addLogoutHandler(getLogoutHandler())
.logoutSuccessHandler(getLogoutSuccessHandler())
.invalidateHttpSession(true)
.and();
}
#Bean
public UsernamePasswordPortalAuthenticationFilter getUsernamePasswordPortalAuthenticationFilter() throws Exception {
UsernamePasswordPortalAuthenticationFilter customFilter = new UsernamePasswordPortalAuthenticationFilter();
customFilter.setAuthenticationManager(authenticationManagerBean());
return customFilter;
}
UsernamePasswordPortalAuthenticationFilter.java:
#PropertySource(value = {"classpath:application.properties"})
public class UsernamePasswordPortalAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
private Logger log = Logger.getLogger(this.getClass());
#Value("${captchaActive}")
private boolean captchaActive;
#Override
public AuthenticationManager getAuthenticationManager() {
return super.getAuthenticationManager();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
return getAuthenticationManager().authenticate(authRequest);
}
/**
* Reads the UsernamePasswordPortalAuthenticationToken from the data of the request.
*
* #param request The request to read the data from.
* #return The authentication token.
* #throws AuthenticationException is thrown if the data cannot be read.
*/
public UsernamePasswordPortalAuthenticationToken getAuthenticationTokenFromRequest(final HttpServletRequest request) throws AuthenticationException {
StringBuffer buf = new StringBuffer();
String line = null;
try {
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null) {
buf.append(line);
}
UsernamePasswordPortalAuthenticationToken loginDataWithCaptcha =
new ObjectMapper().readValue(buf.toString(), UsernamePasswordPortalAuthenticationToken.class);
if (this.captchaActive) {
String answer = (String) request.getSession().getAttribute("COLLPHIRCAPTCHA");
List<CaptchaCookieDto> captchaCookieDtos;
captchaCookieDtos = (List<CaptchaCookieDto>) request.getAttribute("captchaCookies");
CaptchaCookieDto captchaCookieDto = captchaCookieDtos.stream().filter(captchaCookie -> captchaCookie.getUsername().equals(
loginDataWithCaptcha.getUsername())).findAny().orElse(null);
if (captchaCookieDto != null && captchaCookieDto.getCounter() >= 2) {
if (answer.equals(loginDataWithCaptcha.getConfirmCaptcha())) {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
UsernamePasswordPortalAuthenticationToken.class);
} else {
throw new BadCredentialsException("invalid data");
}
} else {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
UsernamePasswordPortalAuthenticationToken.class);
}
} else {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(), UsernamePasswordPortalAuthenticationToken.class);
}
} catch (Exception e) {
throw new BadCredentialsException("invalid data");
}
}
}
I tried changing the order of my two custom filters (CORSFilter and CookieFilter), or putting the CORSFilter somehwere else (addFilterBefore SessionManagementFilter does not work, if I do that, the login-call won't work because of the missing CORS-header) and almost everything else...
I also tried using the idea from the authsuccesshandler from https://www.baeldung.com/spring_redirect_after_login where I just get the requests origin header (which should be the frontend-URL https://localhost:3000) to redirect back to it:
#Component
public class MyTestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger LOG = Logger.getLogger(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public MyTestAuthenticationSuccessHandler() {
super();
setUseReferer(true);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
LOG.info("onAuthenticationSuccess");
SecurityContextHolder.getContext().setAuthentication(auth);
handle(request, response, auth);
}
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
String targetUrl = determineTargetUrl(request);
if (response.isCommitted()) {
LOG.info("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(HttpServletRequest request) {
return request.getHeader("Origin");
}
}
but still it doesn't work.
Also, if I try to debug the backend and set breakpoints inside of the authsuccesshandler and authfailurehandler, it still doesn't stop there. Shouldn't it stop there?
.formLogin()
.loginPage(c440_START_PAGE)
.successHandler(getAuthenticationSuccessHandler())
.failureHandler(getAuthenticationFailureHandler())
.loginProcessingUrl(C440_LOGIN)
.permitAll()
.and();
I really don't understand where this redirect is happening and why it won't use my new authsuccesshandler.
UPDATE 07.03.19: It seems that the successhandler isn't being called at all, even if I deploy both frontend and backend at the same URL as a bundled WAR file which makes the login work again. Weird thing is, even if I remove the .formLogin() stuff from the configure method inside the SecurityConfig the login still works. So I guess it looks like all the magic is happening in the AuthenticationProvider which is being called in our custom UsernamePasswordAuthenticationFilter:
UsernamePasswordAuthenticationFilter
[...]
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
return getAuthenticationManager().authenticate(authRequest);
}
[...]
AuthenticationProvider:
[...]
#Override
public CollphirAuthentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new IllegalArgumentException("authentication");
}
if (UsernamePasswordPortalAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
UsernamePasswordPortalAuthenticationToken clientAuthentication = (UsernamePasswordPortalAuthenticationToken) authentication;
CollphirUser user = getUserService().loginUser(
clientAuthentication.getName(), clientAuthentication.getCredentials().toString(), clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());
CollphirAuthentication auth = null;
if (user == null || user.getBenutzerkennung() == null || user.getCOLRolle() == null) {
LOG.info("authentication failed");
Notification[] notifications = user.getNotifications();
String msg = null;
if (notifications != null && notifications[0] != null && notifications[0].getText() != null) {
msg = notifications[0].getText();
}
throw new BadCredentialsException(msg);
}
Referenz arbeitgeberReference = getArbeitgeberReference(user, clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());
auth = new CollphirAuthentication(user, arbeitgeberReference);
auth.setArbeitgeber(getArbeitgeber( arbeitgeberReference));
LOG.debug("is authenticated: " + auth.isAuthenticated());
return auth;
}
throw new BadCredentialsException("type");
}
[...]
So my guess is: Somewhere inside the UsernamePasswordPortalAuthenticationFilter or AuthenticationProvider the redirect is being made. If I think about it, a redirect doesn't make sense at all in an AngularJS frontend where the call to the backend is being made via REST, right? Shouldn't the backend just send back a status code or something which the AngularJS controller can evaluate to change the state or display an error message?
It looks like the whole login process in this application is really weird. I can't imagine it is usual to not use the .formLogin() and .successHandler()? The thing is, I don't have a best practice example for an AngularJS frontend and Spring Security backend as comparison...
Do you use the Springs default AuthenticationSuccessHandler? I think that this handler just redirect to the application base path and it is not working if your frontend is at another context or URL. So you have to perform a redirect after an successfull login back to your frontend.
Take a look at this page: https://www.baeldung.com/spring-security-redirect-login
Here you find a few possibilities to handle that case.

Spring boot web socket with stomp not sending message to specific user

I am trying to implement a basic chat application with Spring boot & Stomp Protocol. I am not able to send message to specific user via SimpMessagingTemplate.convertAndSendToUser
All of my messages are pushed to all connected sockets.
my controller:
#Controller
public class MessageController {
private final SimpMessagingTemplate simpMessagingTemplate;
/**
* Constructor for object
*
* #param simpMessagingTemplate
*/
public MessageController(final SimpMessagingTemplate simpMessagingTemplate) {
this.simpMessagingTemplate = simpMessagingTemplate;
}
/**
* Responsible for sharing message through web socket.s
*
* #param message
* to share with audience.
* #return
*/
#MessageMapping("/message")
#SendTo("/topic/message")
public Message send(Message message) {
String time = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
message.setTime(time);
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/topic/message", message);
return message;
}
}
web socket configuration:
#EnableWebSocketMessageBroker
#Configuration
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private static final int MESSAGE_BUFFER_SIZE = 8192;
private static final long SECOND_IN_MILLIS = 1000L;
private static final long HOUR_IN_MILLIS = SECOND_IN_MILLIS * 60 * 60;
/*
* (non-Javadoc)
*
* #see org.springframework.web.socket.config.annotation.
* AbstractWebSocketMessageBrokerConfigurer#configureMessageBroker(org.
* springframework.messaging.simp.config.MessageBrokerRegistry)
*/
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
// simple broker is applicable for first setup.
// To scale application enableStompBrokerRelay has to be configured.
// documentation :
// https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-handle-broker-relay
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
/*
* (non-Javadoc)
*
* #see org.springframework.web.socket.config.annotation.
* WebSocketMessageBrokerConfigurer#registerStompEndpoints(org.
* springframework.web.socket.config.annotation.StompEndpointRegistry)
*/
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat");
registry.addEndpoint("/chat").withSockJS();
}
/**
* Bean for servlet container configuration. Sets message buffer size and
* idle timeout.
*
* #return
*/
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(MESSAGE_BUFFER_SIZE);
container.setMaxBinaryMessageBufferSize(MESSAGE_BUFFER_SIZE);
container.setMaxSessionIdleTimeout(HOUR_IN_MILLIS);
container.setAsyncSendTimeout(SECOND_IN_MILLIS);
return container;
}
}
basic security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("1").password("1").roles("USER");
auth.inMemoryAuthentication().withUser("2").password("2").roles("USER");
auth.inMemoryAuthentication().withUser("3").password("3").roles("USER");
}
}
and javascript code snippet:
dataStream = $websocket('ws://localhost:8080/chat');
stomp = Stomp.over(dataStream.socket);
var startListener = function() {
connected = true;
stomp.subscribe('/topic/message', function(data) {
messages.push(JSON.parse(data.body));
listener.notify();
});
};
stomp.connect({
'Login' : name,
passcode : name,
'client-id' : name
}, startListener);
send = function(request) {
stomp.send('/app/message', {}, JSON.stringify(request));
}
You should subscribe the special destination.
stomp.subscribe('/topic/message' + client_id, function(data) {
messages.push(JSON.parse(data.body));
listener.notify();
});
#SendTo("/topic/message") with return will send message to all client subscribe to "/topic/message", meanwhile follow code send message to all client subsribe to "/topic/message/{message.getTo()}":
simpMessagingTemplate.convertAndSendToUser(message.getTo(), "/topic/message", message);

Accept Multipart file upload as camel restlet or cxfrs endpoint

I am looking to implement a route where reslet/cxfrs end point will accept file as multipart request and process. (Request may have some JSON data as well.
Thanks in advance.
Regards.
[EDIT]
Have tried following code. Also tried sending file using curl. I can see file related info in headers and debug output, but not able to retrieve attachment.
from("servlet:///hello").process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
StringBuffer v = new StringBuffer();
HttpServletRequest request = (HttpServletRequest) in
.getHeaders().get(Exchange.HTTP_SERVLET_REQUEST);
DiskFileItemFactory diskFile = new DiskFileItemFactory();
FileItemFactory factory = diskFile;
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(request);
.....
curl :
curl -vvv -i -X POST -H "Content-Type: multipart/form-data" -F "image=#/Users/navaltiger/1.jpg; type=image/jpg" http://:8080/JettySample/camel/hello
following code works (but can't use as it embeds jetty, and we would like to deploy it on tomcat/weblogic)
public void configure() throws Exception {
// getContext().getProperties().put("CamelJettyTempDir", "target");
getContext().setStreamCaching(true);
getContext().setTracing(true);
from("jetty:///test").process(new Processor() {
// from("servlet:///hello").process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
HttpServletRequest request = exchange.getIn().getBody(
HttpServletRequest.class);
StringBuffer v = new StringBuffer();
// byte[] picture = (request.getParameter("image")).getBytes();
v.append("\n Printing All Request Parameters From HttpSerlvetRequest: \n+"+body +" \n\n");
Enumeration<String> requestParameters = request
.getParameterNames();
while (requestParameters.hasMoreElements()) {
String paramName = (String) requestParameters.nextElement();
v.append("\n Request Paramter Name: " + paramName
+ ", Value - " + request.getParameter(paramName));
}
I had a similar problem and managed to resolve inspired by the answer of brentos. The rest endpoint in my case is defined via xml:
<restContext id="UploaderServices" xmlns="http://camel.apache.org/schema/spring">
<rest path="/uploader">
<post bindingMode="off" uri="/upload" produces="application/json">
<to uri="bean:UploaderService?method=uploadData"/>
</post>
</rest>
</restContext>
I had to use "bindingMode=off" to disable xml/json unmarshalling because the HttpRequest body contains multipart data (json/text+file) and obviously the standard unmarshaling process was unable to process the request because it's expecting a string in the body and not a multipart payload.
The file and other parameters are sent from a front end that uses the file upload angular module: https://github.com/danialfarid/ng-file-upload
To solve CORS problems I had to add a CORSFilter filter in the web.xml like the one here:
public class CORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
ServletException {
HttpServletResponse httpResp = (HttpServletResponse) resp;
HttpServletRequest httpReq = (HttpServletRequest) req;
httpResp.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH");
httpResp.setHeader("Access-Control-Allow-Origin", "*");
if (httpReq.getMethod().equalsIgnoreCase("OPTIONS")) {
httpResp.setHeader("Access-Control-Allow-Headers",
httpReq.getHeader("Access-Control-Request-Headers"));
}
chain.doFilter(req, resp);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
#Override
public void destroy() {
}
}
Also, I had to modify a little bit the unmarshaling part:
public String uploadData(Message exchange) {
String contentType=(String) exchange.getIn().getHeader(Exchange.CONTENT_TYPE);
MediaType mediaType = MediaType.valueOf(contentType); //otherwise the boundary parameter is lost
InputRepresentation representation = new InputRepresentation(exchange
.getBody(InputStream.class), mediaType);
try {
List<FileItem> items = new RestletFileUpload(
new DiskFileItemFactory())
.parseRepresentation(representation);
for (FileItem item : items) {
if (!item.isFormField()) {
InputStream inputStream = item.getInputStream();
// Path destination = Paths.get("MyFile.jpg");
// Files.copy(inputStream, destination,
// StandardCopyOption.REPLACE_EXISTING);
System.out.println("found file in request:" + item);
}else{
System.out.println("found string in request:" + new String(item.get(), "UTF-8"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "200";
}
I'm using the Camel REST DSL with Restlet and was able to get file uploads working with the following code.
rest("/images").description("Image Upload Service")
.consumes("multipart/form-data").produces("application/json")
.post().description("Uploads image")
.to("direct:uploadImage");
from("direct:uploadImage")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
MediaType mediaType =
exchange.getIn().getHeader(Exchange.CONTENT_TYPE, MediaType.class);
InputRepresentation representation =
new InputRepresentation(
exchange.getIn().getBody(InputStream.class), mediaType);
try {
List<FileItem> items =
new RestletFileUpload(
new DiskFileItemFactory()).parseRepresentation(representation);
for (FileItem item : items) {
if (!item.isFormField()) {
InputStream inputStream = item.getInputStream();
Path destination = Paths.get("MyFile.jpg");
Files.copy(inputStream, destination,
StandardCopyOption.REPLACE_EXISTING);
}
}
} catch (FileUploadException | IOException e) {
e.printStackTrace();
}
}
});
you can do this with restdsl even if you are not using restlet (exemple jetty) for your restdsl component.
you need to turn restdinding of first for that route and reate two classes to handle the multipart that is in your body.
you need two classes :
DWRequestContext
DWFileUpload
and then you use them in your custom processor
here is the code :
DWRequestContext.java
import org.apache.camel.Exchange;
import org.apache.commons.fileupload.RequestContext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class DWRequestContext implements RequestContext {
private Exchange exchange;
public DWRequestContext(Exchange exchange) {
this.exchange = exchange;
}
public String getCharacterEncoding() {
return StandardCharsets.UTF_8.toString();
}
//could compute here (we have stream cache enabled)
public int getContentLength() {
return (int) -1;
}
public String getContentType() {
return exchange.getIn().getHeader("Content-Type").toString();
}
public InputStream getInputStream() throws IOException {
return this.exchange.getIn().getBody(InputStream.class);
}
}
DWFileUpload.java
import org.apache.camel.Exchange;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import java.util.List;
public class DWFileUpload extends
FileUpload {
public DWFileUpload() {
super();
}
public DWFileUpload(FileItemFactory fileItemFactory) {
super(fileItemFactory);
}
public List<FileItem> parseInputStream(Exchange exchange)
throws FileUploadException {
return parseRequest(new DWRequestContext(exchange));
}
}
you can define your processor like this:
routeDefinition.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
DWFileUpload upload = new DWFileUpload(factory);
java.util.List<FileItem> items = upload.parseInputStream(exchange);
//here I assume I have only one, but I could split it here somehow and link them to camel properties...
//with this, the first file sended with your multipart replaces the body
// of the exchange for the next processor to handle it
exchange.getIn().setBody(items.get(0).getInputStream());
}
});
I stumbled into the same requirement of having to consume a multipart request (containing file data including binary) through Apache Camel Restlet component.
Even though 2.17.x is out, since my project was part of a wider framework / application, I had to be using version 2.12.4.
Initially, my solution drew a lot from restlet-jdbc example yielded data in exchange that although was successfully retrieving text files but I was unable to retrieve correct binary content.
I attempted to dump the data directly into a file to inspect the content using following code (abridged).
from("restlet:/upload?restletMethod=POST")
.to("direct:save-files");
from("direct:save-files")
.process(new org.apache.camel.Processor(){
public void process(org.apache.camel.Exchange exchange){
/*
* Code to sniff exchange content
*/
}
})
.to("file:///C:/<path to a folder>");
;
I used org.apache.commons.fileupload.MultipartStream from apache fileuplaod library to write following utility class to parse Multipart request from a file. It worked successfully when the output of a mulitpart request from Postman was fed to it. However, failed to parse content of the file created by Camel (even through to eyes content of both files looked similar).
public class MultipartParserFileCreator{
public static final String DELIMITER = "\\r?\\n";
public static void main(String[] args) throws Exception {
// taking it from the content-type in exchange
byte[] boundary = "------5lXVNrZvONBWFXxd".getBytes();
FileInputStream fis = new FileInputStream(new File("<path-to-file>"));
extractFile(fis, boundary);
}
public static void extractFile(InputStream is, byte[] boundary) throws Exception {
MultipartStream multipartStream = new MultipartStream(is, boundary, 1024*4, null);
boolean nextPart = multipartStream.skipPreamble();
while (nextPart) {
String headers = multipartStream.readHeaders();
if(isFileContent(headers)) {
String filename = getFileName(headers);
File file = new File("<dir-where-file-created>"+filename);
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
multipartStream.readBodyData(fos);
fos.flush();
fos.close();
}else {
multipartStream.readBodyData(System.out);
}
nextPart = multipartStream.readBoundary();
}
}
public static String[] getContentDispositionTokens(String headersJoined) {
String[] headers = headersJoined.split(DELIMITER, -1);
for(String header: headers) {
System.out.println("Processing header: "+header);
if(header != null && header.startsWith("Content-Disposition:")) {
return header.split(";");
}
}
throw new RuntimeException(
String.format("[%s] header not found in supplied headers [%s]", "Content-Disposition:", headersJoined));
}
public static boolean isFileContent(String header) {
String[] tokens = getContentDispositionTokens(header);
for (String token : tokens) {
if (token.trim().startsWith("filename")) {
return true;
}
}
return false;
}
public static String getFileName(String header) {
String[] tokens = getContentDispositionTokens(header);
for (String token : tokens) {
if (token.trim().startsWith("filename")) {
String filename = token.substring(token.indexOf("=") + 2, token.length()-1);
System.out.println("fileName is " + filename);
return filename;
}
}
return null;
}
}
On debugging through the Camel code, I noticed that at one stage Camel is converting the entire content into String. After a point I had to stop pursuing this approach as there was very little on net applicable for version 2.12.4 and my work was not going anywhere.
Finally, I resorted to following solution
Write an implementation of HttpServletRequestWrapper to allow
multiple read of input stream. One can get an idea from
How to read request.getInputStream() multiple times
Create a filter that uses the above to wrap HttpServletRequest object, reads and extract the file to a directory Convenient way to parse incoming multipart/form-data parameters in a Servlet and attach the path to the request using request.setAttribute() method. With web.xml, configure this filter on restlet servlet
In the process method of camel route, type cast the
exchange.getIn().getBody() in HttpServletRequest object, extract the
attribute (path) use it to read the file as ByteStreamArray for
further processing
Not the cleanest, but I could achieve the objective.

CXF Fault interceptor: log useful information

I would like to log some information in case of fault.
In particular I'd like to log the ip address and port of the client contacting the server, the username if the security in active, and, if possible, also the incoming message.
I added an interceptor in the getOutFaultInterceptors chain of the endpoint, but in the handleMessage I don't know which properties I can use.
Some ideas?
Thank you
In your endpoint xml definition, you could add the following to log incoming messages:
<bean id="logInInterceptor"
class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<jaxws:inInterceptors>
<ref bean="logInInterceptor"/>
</jaxws:inInterceptors>
and then use the bus to limit how many characters you want logged:
<cxf:bus>
<cxf:features>
<cxf:logging limit="102400"/>
</cxf:features>
<cxf:bus>
You haven't mentioned what your method of authentication is, so ff you are using an implementation of UsernameTokenValidator, you could log the incoming username there.
To log details like the client's ip address and port, extend LoggingInInterceptor, then use the following code in handleMessage():
handleMessage() {
HttpServletRequest request =
(HttpServletRequest)message.get(AbstractHTTPDestination.HTTP_REQUEST);
if (null != request) {
String clientAddress = request.getRemoteAddr();
int remotePort = request.getRemotePort();
// log them
}
}
Also have a look at this thread.
I solved in this way
public FaultInterceptor() {
super(Phase.MARSHAL);
}
public void handleMessage(SoapMessage message) throws Fault {
Fault fault = (Fault) message.getContent(Exception.class);
Message inMessage = message.getExchange().getInMessage();
if (inMessage == null) return;
String xmlMessage = null;
InputStream is = inMessage.getContent(InputStream.class);
String rawXml = null;
if (is != null) {
rawXml = is.toString();
}
String username = null;
if (rawXml != null && rawXml.length() > 0) {
try {
XPath xpath = XPathFactory.newInstance().newXPath();
XPathExpression xpathExpression;
xpathExpression = xpath.compile("//*[local-name()=\'Envelope\']/*[local-name()=\'Header\']/*[local-name()=\'Security\']" +
"/*[local-name()=\'UsernameToken\']/*[local-name()=\'Username\']");
InputSource source = new InputSource(new StringReader(rawXml));
username = xpathExpression.evaluate(source);
} catch (XPathExpressionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
xmlMessage = XMLUtils.prittyPrinter(is.toString());
}
String clientAddress = "<unknown>";
int clientPort = -1;
HttpServletRequest request = (HttpServletRequest)inMessage.get(AbstractHTTPDestination.HTTP_REQUEST);
if (null != request) {
clientAddress = request.getRemoteAddr();
clientPort = request.getRemotePort();
}
logger.warn("User: " + username + " [" + clientAddress + ":" + clientPort + "] caused fault: " + fault +
"\nMessage received: \n" + xmlMessage);
}
I found the "inMessage" property and on it I found the original message (and I can retrieve the username) and the "request" from which I retrieved the host and port.
Thank you.
I think you should view the request input stream as consumed when handling the fault.
I suggest you always log the incoming message, and extract some kind of message correlation id - for example username. Keep it as a Message header.
For fault logging, use a fault interceptor which is limited to looking at the input request.
Tie regular + fault logging together with the message correlation id.
Log the full soap request, not just the payload. Soap requests might have headers in addition to the body.
See this question for regular logging, add in addition an output fault interceptor like so:
public class SoapFaultLoggingOutInterceptor extends AbstractPhaseInterceptor<Message> {
private static final String LOCAL_NAME = "MessageID";
private static final int PROPERTIES_SIZE = 128;
private String name = "<interceptor name not set>";
protected Logger logger = null;
protected Level level;
public SoapFaultLoggingOutInterceptor() {
this(LogUtils.getLogger(SoapFaultLoggingOutInterceptor.class), Level.WARNING);
}
public SoapFaultLoggingOutInterceptor(Logger logger, Level reformatSuccessLevel) {
super(Phase.MARSHAL);
this.logger = logger;
this.level = reformatSuccessLevel;
}
public void setName(String name) {
this.name = name;
}
public void handleMessage(Message message) throws Fault {
if (!logger.isLoggable(level)) {
return;
}
StringBuilder buffer = new StringBuilder(PROPERTIES_SIZE);
// perform local logging - to the buffer
buffer.append(name);
logProperties(buffer, message);
logger.log(level, buffer.toString());
}
/**
* Gets theMessageID header in the list of headers.
*
*/
protected String getIdHeader(Message message) {
return getHeader(message, LOCAL_NAME);
}
protected String getHeader(Message message, String name) {
List<Header> headers = (List<Header>) message.get(Header.HEADER_LIST);
if(headers != null) {
for(Header header:headers) {
if(header.getName().getLocalPart().equalsIgnoreCase(name)) {
return header.getObject().toString();
}
}
}
return null;
}
protected void logProperties(StringBuilder buffer, Message message) {
final String messageId = getIdHeader(message);
if(messageId != null) {
buffer.append(" MessageId=");
buffer.append(messageId);
}
Message inMessage = message.getExchange().getInMessage();
HttpServletRequest request = (HttpServletRequest)inMessage.get(AbstractHTTPDestination.HTTP_REQUEST);
buffer.append(" RemoteAddr=");
buffer.append(request.getRemoteAddr());
}
public Logger getLogger() {
return logger;
}
public String getName() {
return name;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
}
where MessageID is the correlation / breadcrumb id.

Resources