Related
I'm trying to write an Http Server using Apache Mina.
According to Mina's architecture, there should be 2 filters for this task, one for Http Request Passing and another for processing the request and generating the response. So using the Mina example codes, I came up with the following code, that has an acceptor, logging filter, Http filter, and a filter for processing request.
Initiation of the server runs correctly, but the request does not come to DummyHttpSever filter. I tried to debug, but could not find the issue. What is going wrong here?
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.api.AbstractIoFilter;
import org.apache.mina.api.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filterchain.ReadFilterChainController;
import org.apache.mina.http.DateUtil;
import org.apache.mina.http.HttpDecoderState;
import org.apache.mina.http.HttpServerDecoder;
import org.apache.mina.http.HttpServerEncoder;
import org.apache.mina.http.api.DefaultHttpResponse;
import org.apache.mina.http.api.HttpContentChunk;
import org.apache.mina.http.api.HttpEndOfContent;
import org.apache.mina.http.api.HttpMethod;
import org.apache.mina.http.api.HttpPdu;
import org.apache.mina.http.api.HttpRequest;
import org.apache.mina.http.api.HttpStatus;
import org.apache.mina.http.api.HttpVersion;
import org.apache.mina.transport.nio.NioTcpServer;
public class HttpTest {
public static void main(String[] args) throws Exception {
NioTcpServer httpServer = new NioTcpServer();
httpServer.setReuseAddress(true);
httpServer.setFilters(new ProtocolCodecFilter<HttpPdu, ByteBuffer, Void, HttpDecoderState>(new HttpServerEncoder(),
new HttpServerDecoder()), new LoggingFilter("DECODED"), new DummyHttpSever());
httpServer.getSessionConfig().setTcpNoDelay(true);
httpServer.bind(new InetSocketAddress(8080));
// run for 20 seconds
Thread.sleep(2000000000);
httpServer.unbind();
}
private static class DummyHttpSever extends AbstractIoFilter {
private HttpRequest incomingRequest;
private List<ByteBuffer> body;
#Override
public void messageReceived(IoSession session, Object message, ReadFilterChainController controller) {
if (message instanceof HttpRequest) {
System.out.println("This shit is working");
incomingRequest = (HttpRequest) message;
body = new ArrayList<ByteBuffer>();
// check if this request is going to be followed by and HTTP body or not
if (incomingRequest.getMethod() != HttpMethod.POST && incomingRequest.getMethod() != HttpMethod.PUT) {
sendResponse(session, incomingRequest);
} else {
}
} else if (message instanceof ByteBuffer) {
body.add((ByteBuffer) message);
} else if (message instanceof HttpEndOfContent) {
// we received all the post content, send the crap back
sendResponse(session, incomingRequest);
}
}
public void sendResponse(IoSession session, HttpRequest request) {
Map<String, String> headers = new HashMap<String, String>();
headers.put("Server", "Apache MINA Dummy test server/0.0.");
headers.put("Date", DateUtil.getCurrentAsString());
headers.put("Connection", "Close");
String strContent = "Hello ! we reply to request !";
ByteBuffer content = ByteBuffer.wrap(strContent.getBytes());
// compute content len
headers.put("Content-Length", String.valueOf(content.remaining()));
session.write(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SUCCESS_OK, headers));
session.write(new HttpContentChunk(content));
session.write(new HttpEndOfContent());
session.close(false);
}
}
}
Also, following are dependencies I am using.
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-http</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-coap</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>LATEST</version>
</dependency>
This is a simple Http web server, which you can modify according to your need. This example is a modification to the example lightweight component of Apache Mina examples.
Main.java
import java.net.InetSocketAddress;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.asyncweb.common.codec.HttpCodecFactory;
import org.apache.asyncweb.examples.lightweight.HttpProtocolHandler;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
public class Main {
public static void main(String[] args) throws Exception {
SocketAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new HttpCodecFactory()));
acceptor.setReuseAddress(true);
acceptor.getSessionConfig().setReuseAddress(true);
acceptor.getSessionConfig().setReceiveBufferSize(1024);
acceptor.getSessionConfig().setSendBufferSize(1024);
acceptor.getSessionConfig().setTcpNoDelay(true);
acceptor.getSessionConfig().setSoLinger(-1);
acceptor.setBacklog(10240);
acceptor.setHandler(new HttpProtocolHandler());
acceptor.bind(new InetSocketAddress(9012));
}
}
HttpProtocalHandler.java
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.core.future.WriteFuture;
import org.apache.asyncweb.common.HttpRequest;
import org.apache.asyncweb.common.HttpResponseStatus;
import org.apache.asyncweb.common.MutableHttpResponse;
import org.apache.asyncweb.common.DefaultHttpResponse;
import org.apache.asyncweb.common.HttpHeaderConstants;
public class HttpProtocolHandler implements IoHandler {
private static final int CONTENT_PADDING = 0; // 101
private final Map<Integer, IoBuffer> buffers = new ConcurrentHashMap<Integer, IoBuffer>();
private final Timer timer;
public HttpProtocolHandler() {
timer = new Timer(true);
}
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
if (!(cause instanceof IOException)) {
cause.printStackTrace();
}
session.close();
}
public Dictionary extractParameters(Map hashParameters){
Dictionary parameters = new Hashtable();
Iterator it = hashParameters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
parameters.put(pair.getKey(), ((ArrayList) pair.getValue()).get(0) );
// it.remove(); // avoids a ConcurrentModificationException
}
return parameters;
}
public void messageReceived(IoSession session, Object message)
throws Exception {
HttpRequest req = (HttpRequest) message;
String path = req.getRequestUri().getPath(); //path: /echo
String end_point = path;
Dictionary parameters = this.extractParameters(req.getParameters());
String response = "";
/* switch (end_point) {
case "/io":
response= new IOHandler().handleRequest(parameters);
break;
case "/cpu":
response= new CPUHandler().handleRequest(parameters);
break;
case "/db":
response= new DBHandler().handleRequest(parameters);
break;
case "/memory":
response= new MemoryHandler().handleRequest(parameters);
break;
default:
response = "No end point found";
} */
response = "No end point found";
MutableHttpResponse res;
// if (path.startsWith("/size/")) {
// doDataResponse(session, req);
// } else if (path.startsWith("/delay/")) {
// doAsynchronousDelayedResponse(session, req);
// } else if (path.startsWith("/adelay/")) {
// doAsynchronousDelayedResponse(session, req);
// } else {
res = new DefaultHttpResponse();
IoBuffer bb = IoBuffer.allocate(1024);
bb.setAutoExpand(true);
bb.putString(response.toString(), Charset.forName("UTF-8").newEncoder());
bb.flip();
res.setContent(bb);
// res.setHeader("Pragma", "no-cache");
// res.setHeader("Cache-Control", "no-cache");
res.setStatus(HttpResponseStatus.OK);
WriteFuture future = session.write(res);
if (!HttpHeaderConstants.VALUE_KEEP_ALIVE.equalsIgnoreCase(
res.getHeader( HttpHeaderConstants.KEY_CONNECTION))) {
future.addListener(IoFutureListener.CLOSE);
}
}
private void writeResponse(IoSession session, HttpRequest req,
MutableHttpResponse res) {
res.normalize(req);
WriteFuture future = session.write(res);
if (!HttpHeaderConstants.VALUE_KEEP_ALIVE.equalsIgnoreCase(
res.getHeader( HttpHeaderConstants.KEY_CONNECTION))) {
future.addListener(IoFutureListener.CLOSE);
}
}
private void doDataResponse(IoSession session, HttpRequest req) {
String path = req.getRequestUri().getPath();
int size = Integer.parseInt(path.substring(path.lastIndexOf('/') + 1))
+ CONTENT_PADDING;
MutableHttpResponse res = new DefaultHttpResponse();
res.setStatus(HttpResponseStatus.OK);
res.setHeader("ETag", "W/\"" + size + "-1164091960000\"");
res.setHeader("Last-Modified", "Tue, 31 Nov 2006 06:52:40 GMT");
IoBuffer buf = buffers.get(size);
if (buf == null) {
buf = IoBuffer.allocate(size);
buffers.put(size, buf);
}
res.setContent(buf.duplicate());
writeResponse(session, req, res);
}
private void doAsynchronousDelayedResponse(final IoSession session,
final HttpRequest req) {
String path = req.getRequestUri().getPath();
int delay = Integer.parseInt(path.substring(path.lastIndexOf('/') + 1));
final MutableHttpResponse res = new DefaultHttpResponse();
res.setStatus(HttpResponseStatus.OK);
res.setHeader("ETag", "W/\"0-1164091960000\"");
res.setHeader("Last-Modified", "Tue, 31 Nov 2006 06:52:40 GMT");
timer.schedule(new TimerTask() {
#Override
public void run() {
writeResponse(session, req, res);
}
}, delay);
}
public void messageSent(IoSession session, Object message) throws Exception {
}
public void sessionClosed(IoSession session) throws Exception {
}
public void sessionCreated(IoSession session) throws Exception {
}
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
session.close();
}
public void sessionOpened(IoSession session) throws Exception {
session.getConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);
}
}
We have JAX RS implementation which needs to send back JSON output. But the response size is huge. And the client expects the same synchronously.
Hence I tried to use StreamingOutput... but the client is not really getting the data in chunks.
Below is sample snippet:
Server Side
streamingOutput = new StreamingOutput() {
#Override
public void write(OutputStream out) throws IOException, WebApplicationException {
JsonGenerator jsonGenerator = mapper.getFactory().createGenerator(out);
jsonGenerator.writeStartArray();
for(int i=0; i < 10; i++) {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("Response_State", "Response State - " + i);
jsonGenerator.writeStringField("Response_Report", "Response Report - " + i);
jsonGenerator.writeStringField("Error_details", "Error Details - " + i);
jsonGenerator.writeEndObject();;
jsonGenerator.flush();
try {
Thread.currentThread().sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
jsonGenerator.writeEndArray();
jsonGenerator.close();
}
};
return Response.status(200).entity(streamingOutput).build();
Client
HttpClient client = HttpClientBuilder.create().build();
HttpPost post = new HttpPost("http://localhost:8080/AccessData/FetchReport");
post.setHeader("Content-type", "application/json");
ResponseHandler<HttpResponse> responseHandler = new BasicResponseHandler();
StringEntity entity = new StringEntity(jsonRequest); //jsonRequest is request string
post.setEntity(entity);
HttpResponse response = client.execute(post);
BufferedReader buffReader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
JsonParser jsonParser = new JsonFactory().createParser(buffReader);
while(jsonParser.nextToken() != JsonToken.END_OBJECT) {
System.out.println(jsonParser.getCurrentName() + ":" + jsonParser.getCurrentValue());
}
String output;
while((output = buffReader.readLine()) != null) {
System.out.println(output);
}
In the server side code, I am putting sleep call just to simulate a gap between chunks of data. What I need is that the client should receive chunks of data as and when it is thrown back by the server.
But here the client gets the response in entirety always.
Any possible solution?
Thanks in advance.
It looks like the client side is not implemented correctly: reading the array of the objects using the parser.
Also, I would like to recommend reading and writing a data transfer object instead of low level field-by-field reading and writing.
For the sake of completeness, here is a complete draft example that uses: Jersey 2.25.1, Jetty 9.2.14.v20151106.
Common
ResponseData class
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class ResponseData {
private final String responseState;
private final String responseReport;
private final String errorDetails;
#JsonCreator
public ResponseData(
#JsonProperty("Response_State") final String responseState,
#JsonProperty("Response_Report") final String responseReport,
#JsonProperty("Error_details") final String errorDetails) {
this.responseState = responseState;
this.responseReport = responseReport;
this.errorDetails = errorDetails;
}
public String getResponseState() {
return this.responseState;
}
public String getResponseReport() {
return this.responseReport;
}
public String getErrorDetails() {
return this.errorDetails;
}
#Override
public String toString() {
return String.format(
"ResponseData: responseState: %s; responseReport: %s; errorDetails: %s",
this.responseState,
this.responseReport,
this.errorDetails
);
}
}
Service
ServerProgram class
import java.net.URI;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.jetty.JettyHttpContainerFactory;
import org.glassfish.jersey.server.ResourceConfig;
public class ServerProgram {
public static void main(final String[] args) {
final URI uri = URI.create("http://localhost:8080/");
final ResourceConfig resourceConfig = new ResourceConfig(TestResource.class);
resourceConfig.register(JacksonFeature.class);
JettyHttpContainerFactory.createServer(uri, resourceConfig);
}
}
TestResource class
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.OutputStream;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
#Path("/")
public class TestResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getData() {
final StreamingOutput streamingOutput = new JsonStreamingOutput();
return Response.status(200).entity(streamingOutput).build();
}
private static class JsonStreamingOutput implements StreamingOutput {
#Override
public void write(final OutputStream outputStream) throws IOException, WebApplicationException {
final ObjectMapper objectMapper = new ObjectMapper();
final JsonFactory jsonFactory = objectMapper.getFactory();
try (final JsonGenerator jsonGenerator = jsonFactory.createGenerator(outputStream)) {
jsonGenerator.writeStartArray();
for (int i = 0; i < 10; i++) {
final ResponseData responseData = new ResponseData(
"Response State - " + i,
"Response Report - " + i,
"Error Details - " + i
);
jsonGenerator.writeObject(responseData);
jsonGenerator.flush();
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
jsonGenerator.writeEndArray();
}
}
}
}
Client
ClientProgram class
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.MediaType;
import org.glassfish.jersey.client.ClientProperties;
public class ClientProgram {
public static void main(final String[] args) throws IOException {
Client client = null;
try {
client = ClientBuilder.newClient();
client.property(ClientProperties.READ_TIMEOUT, 10000);
try (final InputStream inputStream = client
.target("http://localhost:8080/")
.request(MediaType.APPLICATION_JSON)
.get(InputStream.class);
final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
processStream(bufferedInputStream);
}
} finally {
if (client != null) {
client.close();
}
}
}
private static void processStream(final InputStream inputStream) throws IOException {
final ObjectMapper objectMapper = new ObjectMapper();
final JsonFactory jsonFactory = objectMapper.getFactory();
try (final JsonParser jsonParser = jsonFactory.createParser(inputStream)) {
final JsonToken arrayToken = jsonParser.nextToken();
if (arrayToken == null) {
// TODO: Return or throw exception.
return;
}
if (!JsonToken.START_ARRAY.equals(arrayToken)) {
// TODO: Return or throw exception.
return;
}
// Iterate through the objects of the array.
while (JsonToken.START_OBJECT.equals(jsonParser.nextToken())) {
final ResponseData responseData = jsonParser.readValueAs(ResponseData.class);
System.out.println(responseData);
}
}
}
}
Hope this helps.
I am trying to authenticate with a kerberos/HTTP host. Using Apache HttpClient as my client - and a slightly modified version of this source.
My Kerberos authentication goes perfectly fine, and I wish to know how to set the login credentials programatically. At the moment, the credentials are entered manually through the console, but I want to have it chosen by me at run time. [ As I wish to automate and load test the server with a large number of users, actually. ].
EDIT : Here is a code snippet of the relevant parts :
..
NegotiateSchemeFactory nsf = new NegotiateSchemeFactory();
httpclient.getAuthSchemes().register(AuthPolicy.SPNEGO, nsf);
Credentials use_jaas_creds = new Credentials() {
public String getPassword() {
return null;
}
public Principal getUserPrincipal() {
return null;
}
};
httpclient.getCredentialsProvider().setCredentials(
new AuthScope(null, -1, null),
use_jaas_creds);
HttpUriRequest request = new HttpGet("http://kerberoshost/");
HttpResponse response = httpclient.execute(request);
..
The interface Credentials has two methods - getPassword() and getUserPrincipal(), but from some debugging I did, they don't seem to be invoked at all.
What am I missing here ? What is a cleaner way to statically set the credentials ?
A very similar question had been asked before, but keytabs/login.conf hack is too cumbersome and not a practical option for an automated load test with a large number of user credentials.
Appreciate any help on this.
Because of SPNEGO the snippet code you post (Credentials class stuff setup) is not used by httpclient to authenticate.
You can use a DoAs + a CallBackhandler to pass user & password at runtime.
Then you need a login.conf or whatever the name with this inside:
KrbLogin{
com.sun.security.auth.module.Krb5LoginModule required doNotPrompt=false debug=true useTicketCache=false;
};
You can change the name from "KrbLogin" to the name you like (remember to use the same name in your java code)
and set this with java system properties:
System.setProperty("java.security.auth.login.config", "login.conf");
or with a
-Djava.security.auth.login.config=login.config
Then you need a krb5 config file (usually krb5.ini or krb5.conf with correct configuration inside)
If your workstation (or server) is properly configured for Kerberos this class should works as is (with propper file login.conf and krb5.ini) I used httpclient 4.3.3 and java 1.7 to test it:
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.io.IOException;
import java.security.AccessController;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Set;
public class HttpClientKerberosDoAS {
public static void main(String[] args) throws Exception {
System.setProperty("java.security.auth.login.config", "login.conf");
System.setProperty("java.security.krb5.conf", "krb5.conf");
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
String user = "";
String password = "";
String url = "";
if (args.length == 3) {
user = args[0];
password = args[1];
url = args[2];
HttpClientKerberosDoAS kcd = new HttpClientKerberosDoAS();
System.out.println("Loggin in with user [" + user + "] password [" + password + "] ");
kcd.test(user, password, url);
} else {
System.out.println("run with User Password URL");
}
}
public void test(String user, String password, final String url) {
try {
LoginContext loginCOntext = new LoginContext("KrbLogin", new KerberosCallBackHandler(user, password));
loginCOntext.login();
PrivilegedAction sendAction = new PrivilegedAction() {
#Override
public Object run() {
try {
Subject current = Subject.getSubject(AccessController.getContext());
System.out.println("----------------------------------------");
Set<Principal> principals = current.getPrincipals();
for (Principal next : principals) {
System.out.println("DOAS Principal: " + next.getName());
}
System.out.println("----------------------------------------");
call(url);
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
};
Subject.doAs(loginCOntext.getSubject(), sendAction);
} catch (LoginException le) {
le.printStackTrace();
}
}
private void call(String url) throws IOException {
HttpClient httpclient = getHttpClient();
try {
HttpUriRequest request = new HttpGet(url);
HttpResponse response = httpclient.execute(request);
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println("STATUS >> " + response.getStatusLine());
if (entity != null) {
System.out.println("RESULT >> " + EntityUtils.toString(entity));
}
System.out.println("----------------------------------------");
EntityUtils.consume(entity);
} finally {
httpclient.getConnectionManager().shutdown();
}
}
private HttpClient getHttpClient() {
Credentials use_jaas_creds = new Credentials() {
public String getPassword() {
return null;
}
public Principal getUserPrincipal() {
return null;
}
};
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(null, -1, null), use_jaas_creds);
Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create().register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true)).build();
CloseableHttpClient httpclient = HttpClients.custom().setDefaultAuthSchemeRegistry(authSchemeRegistry).setDefaultCredentialsProvider(credsProvider).build();
return httpclient;
}
class KerberosCallBackHandler implements CallbackHandler {
private final String user;
private final String password;
public KerberosCallBackHandler(String user, String password) {
this.user = user;
this.password = password;
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nc = (NameCallback) callback;
nc.setName(user);
} else if (callback instanceof PasswordCallback) {
PasswordCallback pc = (PasswordCallback) callback;
pc.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callback, "Unknown Callback");
}
}
}
}
}
Note:
you can use:
System.setProperty("sun.security.krb5.debug", "true");
or:
-Dsun.security.krb5.debug=true
to investigate problems.
I am using fineuploader within a struts system but am having trouble getting the file list in the server code.
My jsp contains the following code:
$("#fine-uploader").fineUploader({
debug: true,
request: {
endpoint: '/NRGI/act_MultiPhotoUpload.do'
},
deleteFile: {
enabled: true,
endpoint: '/uploads'
},
retry: {
enableAuto: true
}
});
with the following div near the bottom of the page:
<div id="fine-uploader"></div>
The action actMultiPhotoUpload points to a class via the struts.config.xml file:
<action path="/act_MultiPhotoUpload" name="FileUploadForm" scope="request" validate="true"
type="com.solarcentury.nrgi.document.action.MultiUploadAction"
input="/D5_PhotoLibrary.jsp">
</action>
The class MultiUploadAction is taken from your UploadReceiver and is as follows:
public class MultiUploadAction extends Action {
static Static_Env_Object seo = new Static_Env_Object();
private String UPLOAD_NOT_ALLOWED = "exe";
private EnvUtils eu;
// JUST FOR TESTING
private static final File UPLOAD_DIR = new File("Test/uploads");
private static File TEMP_DIR = new File("Test/uploadsTemp");
private static String CONTENT_LENGTH = "Content-Length";
private static int SUCCESS_RESPONSE_CODE = 200;
#Override
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
HttpSession session = request.getSession();
String sId = session.getId();
eu = new EnvUtils(seo.get_Env_Name(), this.getClass().getSimpleName());
/* **************************************** */
/* * The code for the session timeout check */
/* **************************************** */
if (session.getAttribute("SESS_User") == null) {
eu.log("NO SESSION", "Session timed out...");
return (mapping.findForward("globaltimeout"));
}
UserObject suo = new UserObj_Util(session).get_SessUser();
WebAlertMessages wam = new WebAlertMessages(request, suo.get_Language_ID());
DemonUtil du = new DemonUtil(seo.get_Env_Name());
// DateUtils dateUtil = new DateUtils();
RequestParser requestParser = null;
boolean isIframe = request.getHeader("X-Requested-With") == null || !request.getHeader("X-Requested-With").equals("XMLHttpRequest");
try
{
// resp.setContentType(isIframe ? "text/html" : "text/plain");
response.setContentType("text/plain");
response.setStatus(SUCCESS_RESPONSE_CODE);
// resp.addHeader("Access-Control-Allow-Origin", "http://192.168.130.118:8080");
// resp.addHeader("Access-Control-Allow-Credentials", "true");
// resp.addHeader("Access-Control-Allow-Origin", "*");
if (ServletFileUpload.isMultipartContent(request))
{
MultipartUploadParser multipartUploadParser = new MultipartUploadParser(request, TEMP_DIR, request.getSession().getServletContext());
requestParser = RequestParser.getInstance(request, multipartUploadParser);
writeFileForMultipartRequest(requestParser);
writeResponse(response.getWriter(), requestParser.generateError() ? "Generated error" : null, isIframe, false, requestParser);
}
else
{
requestParser = RequestParser.getInstance(request, null);
//handle POST delete file request
if (requestParser.getMethod() != null
&& requestParser.getMethod().equalsIgnoreCase("DELETE"))
{
String uuid = requestParser.getUuid();
handleDeleteFileRequest(uuid, response);
}
else
{
writeFileForNonMultipartRequest(request, requestParser);
writeResponse(response.getWriter(), requestParser.generateError() ? "Generated error" : null, isIframe, false, requestParser);
}
}
} catch (Exception e)
{
eu.log("UploadReceiver","Problem handling upload request" + e);
if (e instanceof MultiUploadAction.MergePartsException)
{
writeResponse(response.getWriter(), e.getMessage(), isIframe, true, requestParser);
}
else
{
writeResponse(response.getWriter(), e.getMessage(), isIframe, false, requestParser);
}
}
return (new ActionForward(mapping.getInput()));
}
And I use the MultipartUploadParser, RequestParser from the server java examples on the website.
When I step through the code, how ever many files I select to upload, the file list is always empty. Obviously I am doing something stupid here, but would appreciate any guidance please.
I have seen a similar support question where the asker was having trouble getting the filelist, also using struts, but there wasn't an answer against the question
Added on 14/11/2013
The full code is as follows:
The full listing of MultiUploadAction.java is as follows:
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.solarcentury.nrgi.document.action;
/**
*
* #author Roy
*/
import DemonWeb.DmForms.FileUploadForm;
import DemonWeb.DmLogic.DemonUtil;
import DemonWeb.DmLogic.Project;
import DemonWeb.DmSession.Static_Env_Object;
import DemonWeb.DmSession.UserObj_Util;
import DemonWeb.DmSession.UserObject;
import DemonWeb.DmSession.WebAlertMessages;
import DemonWeb.Utils.EnvUtils;
import com.solarcentury.nrgi.document.bean.Document;
import com.solarcentury.nrgi.document.logic.DocumentController;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.Enumeration;
import java.util.Collections;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.upload.FormFile;
/**
*
* #author ajantham
*/
public class MultiUploadAction extends Action {
static Static_Env_Object seo = new Static_Env_Object();
private String UPLOAD_NOT_ALLOWED = "exe";
private EnvUtils eu;
// JUST FOR TESTING
private static final File UPLOAD_DIR = new File("uploads");
private static File TEMP_DIR = new File("uploadsTemp");
private static String CONTENT_LENGTH = "Content-Length";
private static int SUCCESS_RESPONSE_CODE = 200;
#Override
public ActionForward perform(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
HttpSession session = request.getSession();
String sId = session.getId();
eu = new EnvUtils(seo.get_Env_Name(), this.getClass().getSimpleName());
/* **************************************** */
/* * The code for the session timeout check */
/* **************************************** */
if (session.getAttribute("SESS_User") == null) {
eu.log("NO SESSION", "Session timed out...");
return (mapping.findForward("globaltimeout"));
}
UserObject suo = new UserObj_Util(session).get_SessUser();
WebAlertMessages wam = new WebAlertMessages(request, suo.get_Language_ID());
DemonUtil du = new DemonUtil(seo.get_Env_Name());
// DateUtils dateUtil = new DateUtils();
RequestParser requestParser = null;
boolean isIframe = request.getHeader("X-Requested-With") == null || !request.getHeader("X-Requested-With").equals("XMLHttpRequest");
try
{
// resp.setContentType(isIframe ? "text/html" : "text/plain");
response.setContentType("text/plain");
response.setStatus(SUCCESS_RESPONSE_CODE);
// resp.addHeader("Access-Control-Allow-Origin", "http://192.168.130.118:8080");
// resp.addHeader("Access-Control-Allow-Credentials", "true");
// resp.addHeader("Access-Control-Allow-Origin", "*");
if (ServletFileUpload.isMultipartContent(request))
{
MultipartUploadParser multipartUploadParser = new MultipartUploadParser(request, TEMP_DIR, request.getSession().getServletContext());
requestParser = RequestParser.getInstance(request, multipartUploadParser);
writeFileForMultipartRequest(requestParser);
writeResponse(response.getWriter(), requestParser.generateError() ? "Generated error" : null, isIframe, false, requestParser);
}
else
{
requestParser = RequestParser.getInstance(request, null);
//handle POST delete file request
if (requestParser.getMethod() != null
&& requestParser.getMethod().equalsIgnoreCase("DELETE"))
{
String uuid = requestParser.getUuid();
handleDeleteFileRequest(uuid, response);
}
else
{
writeFileForNonMultipartRequest(request, requestParser);
writeResponse(response.getWriter(), requestParser.generateError() ? "Generated error" : null, isIframe, false, requestParser);
}
}
} catch (Exception e)
{
eu.log("UploadReceiver","Problem handling upload request" + e);
if (e instanceof MultiUploadAction.MergePartsException)
{
writeResponse(response.getWriter(), e.getMessage(), isIframe, true, requestParser);
}
else
{
writeResponse(response.getWriter(), e.getMessage(), isIframe, false, requestParser);
}
}
return (new ActionForward(mapping.getInput()));
}
public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
String uuid = req.getPathInfo().replaceAll("/", "");
handleDeleteFileRequest(uuid, resp);
}
private void handleDeleteFileRequest(String uuid, HttpServletResponse resp) throws IOException
{
FileUtils.deleteDirectory(new File(UPLOAD_DIR, uuid));
if (new File(UPLOAD_DIR, uuid).exists())
{
eu.log("UploadReceiver","couldn't find or delete " + uuid);
}
else
{
eu.log("UploadReceiver","deleted " + uuid);
}
resp.setStatus(SUCCESS_RESPONSE_CODE);
// resp.addHeader("Access-Control-Allow-Origin", "*");
}
private void writeFileForNonMultipartRequest(HttpServletRequest req, RequestParser requestParser) throws Exception
{
File dir = new File(UPLOAD_DIR, requestParser.getUuid());
dir.mkdirs();
String contentLengthHeader = req.getHeader(CONTENT_LENGTH);
long expectedFileSize = Long.parseLong(contentLengthHeader);
if (requestParser.getPartIndex() >= 0)
{
writeFile(req.getInputStream(), new File(dir, requestParser.getUuid() + "_" + String.format("%05d", requestParser.getPartIndex())), null);
if (requestParser.getTotalParts()-1 == requestParser.getPartIndex())
{
File[] parts = getPartitionFiles(dir, requestParser.getUuid());
File outputFile = new File(dir, requestParser.getFilename());
for (File part : parts)
{
mergeFiles(outputFile, part);
}
assertCombinedFileIsVaid(requestParser.getTotalFileSize(), outputFile, requestParser.getUuid());
deletePartitionFiles(dir, requestParser.getUuid());
}
}
else
{
writeFile(req.getInputStream(), new File(dir, requestParser.getFilename()), expectedFileSize);
}
}
private void writeFileForMultipartRequest(RequestParser requestParser) throws Exception
{
File dir = new File(UPLOAD_DIR, requestParser.getUuid());
dir.mkdirs();
if (requestParser.getPartIndex() >= 0)
{
writeFile(requestParser.getUploadItem().getInputStream(), new File(dir, requestParser.getUuid() + "_" + String.format("%05d", requestParser.getPartIndex())), null);
if (requestParser.getTotalParts()-1 == requestParser.getPartIndex())
{
File[] parts = getPartitionFiles(dir, requestParser.getUuid());
File outputFile = new File(dir, requestParser.getOriginalFilename());
for (File part : parts)
{
mergeFiles(outputFile, part);
}
assertCombinedFileIsVaid(requestParser.getTotalFileSize(), outputFile, requestParser.getUuid());
deletePartitionFiles(dir, requestParser.getUuid());
}
}
else
{
writeFile(requestParser.getUploadItem().getInputStream(), new File(dir, requestParser.getFilename()), null);
}
}
private void assertCombinedFileIsVaid(int totalFileSize, File outputFile, String uuid) throws MultiUploadAction.MergePartsException
{
if (totalFileSize != outputFile.length())
{
deletePartitionFiles(UPLOAD_DIR, uuid);
outputFile.delete();
throw new MultiUploadAction.MergePartsException("Incorrect combined file size!");
}
}
private static class PartitionFilesFilter implements FilenameFilter
{
private String filename;
PartitionFilesFilter(String filename)
{
this.filename = filename;
}
#Override
public boolean accept(File file, String s)
{
return s.matches(Pattern.quote(filename) + "_\\d+");
}
}
private static File[] getPartitionFiles(File directory, String filename)
{
File[] files = directory.listFiles(new MultiUploadAction.PartitionFilesFilter(filename));
Arrays.sort(files);
return files;
}
private static void deletePartitionFiles(File directory, String filename)
{
File[] partFiles = getPartitionFiles(directory, filename);
for (File partFile : partFiles)
{
partFile.delete();
}
}
private File mergeFiles(File outputFile, File partFile) throws IOException
{
FileOutputStream fos = new FileOutputStream(outputFile, true);
try
{
FileInputStream fis = new FileInputStream(partFile);
try
{
IOUtils.copy(fis, fos);
}
finally
{
IOUtils.closeQuietly(fis);
}
}
finally
{
IOUtils.closeQuietly(fos);
}
return outputFile;
}
private File writeFile(InputStream in, File out, Long expectedFileSize) throws IOException
{
FileOutputStream fos = null;
try
{
fos = new FileOutputStream(out);
IOUtils.copy(in, fos);
if (expectedFileSize != null)
{
Long bytesWrittenToDisk = out.length();
if (!expectedFileSize.equals(bytesWrittenToDisk))
{
eu.log("UploadReceiver","Expected file {} to be {} bytes; file on disk is {} bytes " + new Object[] { out.getAbsolutePath(), expectedFileSize, 1 });
out.delete();
throw new IOException(String.format("Unexpected file size mismatch. Actual bytes %s. Expected bytes %s.", bytesWrittenToDisk, expectedFileSize));
}
}
return out;
}
catch (Exception e)
{
throw new IOException(e);
}
finally
{
IOUtils.closeQuietly(fos);
}
}
private void writeResponse(PrintWriter writer, String failureReason, boolean isIframe, boolean restartChunking, RequestParser requestParser)
{
if (failureReason == null)
{
// if (isIframe)
// {
// writer.print("{\"success\": true, \"uuid\": \"" + requestParser.getUuid() + "\"}<script src=\"http://192.168.130.118:8080/client/js/iframe.xss.response.js\"></script>");
// }
// else
// {
writer.print("{\"success\": true}");
// }
}
else
{
if (restartChunking)
{
writer.print("{\"error\": \"" + failureReason + "\", \"reset\": true}");
}
else
{
// if (isIframe)
// {
// writer.print("{\"error\": \"" + failureReason + "\", \"uuid\": \"" + requestParser.getUuid() + "\"}<script src=\"http://192.168.130.118:8080/client/js/iframe.xss.response.js\"></script>");
// }
// else
// {
writer.print("{\"error\": \"" + failureReason + "\"}");
// }
}
}
}
private class MergePartsException extends Exception
{
MergePartsException(String message)
{
super(message);
}
}
}
The full source of MultipartUploadParser.java is as follows:
package com.solarcentury.nrgi.document.action;
import DemonWeb.DmSession.Static_Env_Object;
import DemonWeb.Utils.EnvUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.FileCleanerCleanup;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileCleaningTracker;
import org.apache.commons.lang3.StringUtils;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.*;
public class MultipartUploadParser
{
// final Logger log = LoggerFactory.getLogger(MultipartUploadParser.class);
static Static_Env_Object seo = new Static_Env_Object();
private EnvUtils eu;
private Map<String, String> params = new HashMap<String, String>();
private List<FileItem> files = new ArrayList<FileItem>();
// fileItemsFactory is a field (even though it's scoped to the constructor) to prevent the
// org.apache.commons.fileupload.servlet.FileCleanerCleanup thread from attempting to delete the
// temp file before while it is still being used.
//
// FileCleanerCleanup uses a java.lang.ref.ReferenceQueue to delete the temp file when the FileItemsFactory marker object is GCed
private DiskFileItemFactory fileItemsFactory;
public MultipartUploadParser(HttpServletRequest request, File repository, ServletContext context) throws Exception
{
this.eu = new EnvUtils(seo.get_Env_Name(), "MultipartUploadParser " + "1.0.0.0");
if (!repository.exists() && !repository.mkdirs())
{
throw new IOException("Unable to mkdirs to " + repository.getAbsolutePath());
}
fileItemsFactory = setupFileItemFactory(repository, context);
ServletFileUpload upload = new ServletFileUpload(fileItemsFactory);
List<FileItem> formFileItems = upload.parseRequest(request);
parseFormFields(formFileItems);
if (files.isEmpty())
{
eu.log("MultipartUploadParser","No files were found when processing the request. Debugging info follows");
writeDebugInfo(request);
throw new FileUploadException("No files were found when processing the request.");
}
else
{
writeDebugInfo(request);
}
}
private DiskFileItemFactory setupFileItemFactory(File repository, ServletContext context)
{
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD);
factory.setRepository(repository);
FileCleaningTracker pTracker = FileCleanerCleanup.getFileCleaningTracker(context);
factory.setFileCleaningTracker(pTracker);
return factory;
}
private void writeDebugInfo(HttpServletRequest request)
{
eu.log("MultipartUploadParser","-- POST HEADERS --");
for (String header : Collections.list((Enumeration<String>) request.getHeaderNames()))
{
eu.log("MultipartUploadParser", header + "header " + request.getHeader(header));
}
eu.log("MultipartUploadParser","-- POST PARAMS --");
for (String key : params.keySet())
{
eu.log("MultipartUploadParser", key + " key " + params.get(key));
}
}
private void parseFormFields(List<FileItem> items) throws UnsupportedEncodingException
{
for (FileItem item : items)
{
if (item.isFormField())
{
String key = item.getFieldName();
String value = item.getString("UTF-8");
if (StringUtils.isNotBlank(key))
{
params.put(key, StringUtils.defaultString(value));
}
}
else
{
files.add(item);
}
}
}
public Map<String, String> getParams()
{
return params;
}
public List<FileItem> getFiles()
{
if (files.isEmpty())
{
throw new RuntimeException("No FileItems exist.");
}
return files;
}
public FileItem getFirstFile()
{
if (files.isEmpty())
{
throw new RuntimeException("No FileItems exist.");
}
return files.iterator().next();
}
}
MultiUploadAction decides that the request isMultipartContent and so calls MultipartUploadParser. This class successfully
checks the directory structure and then uses its method ParseFormFields to buld up a list of files.
However it does not find any files or form fields, and so on line 62 of MultipartUploadParser files.isEmpty() is true,
and so an exception is thrown (line 70)
It doesn't matter how many files I select in the client, the file list is always empty.
Many thanks for your help in this - much appreciated
I know this is very old, but I ran into this same problem. In my implementation I had tried to adapt the example endpoint code into my Spring MVC project. I discovered that the Spring MVC framework was actually calling the same Apache ServletFileUpload.parseRequest BEFORE passing the request to my controller and detecting the multipart, fetching the params, etc. The framework was parsing the multipart request despite whether I was using MutlipartHttpServlet request, or simply HttpServletRequest as my signature for the controller method. When my code in the MultipartUploadParser hit ServletFileUpload.parseRequest again, this time it returned an empty List because they had already been parsed.
The endpoint example code in the git repo for Fine Uploader works as it is, it was just my adaptation with Spring MVC that didn't work.
I would like to modify an outgoing SOAP Request.
I would like to remove 2 xml nodes from the Envelope's body.
I managed to set up an Interceptor and get the generated String value of the message set to the endpoint.
However, the following code does not seem to work as the outgoing message is not edited as expected. Does anyone have some code or ideas on how to do this?
public class MyOutInterceptor extends AbstractSoapInterceptor {
public MyOutInterceptor() {
super(Phase.SEND);
}
public void handleMessage(SoapMessage message) throws Fault {
// Get message content for dirty editing...
StringWriter writer = new StringWriter();
CachedOutputStream cos = (CachedOutputStream)message.getContent(OutputStream.class);
InputStream inputStream = cos.getInputStream();
IOUtils.copy(inputStream, writer, "UTF-8");
String content = writer.toString();
// remove the substrings from envelope...
content = content.replace("<idJustification>0</idJustification>", "");
content = content.replace("<indicRdv>false</indicRdv>", "");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(content.getBytes(Charset.forName("UTF-8")));
message.setContent(OutputStream.class, outputStream);
}
Based on the first comment, I created an abstract class which can easily be used to change the whole soap envelope.
Just in case someone wants a ready-to-use code part.
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.log4j.Logger;
/**
* http://www.mastertheboss.com/jboss-web-services/apache-cxf-interceptors
* http://stackoverflow.com/questions/6915428/how-to-modify-the-raw-xml-message-of-an-outbound-cxf-request
*
*/
public abstract class MessageChangeInterceptor extends AbstractPhaseInterceptor<Message> {
public MessageChangeInterceptor() {
super(Phase.PRE_STREAM);
addBefore(SoapPreProtocolOutInterceptor.class.getName());
}
protected abstract Logger getLogger();
protected abstract String changeOutboundMessage(String currentEnvelope);
protected abstract String changeInboundMessage(String currentEnvelope);
public void handleMessage(Message message) {
boolean isOutbound = false;
isOutbound = message == message.getExchange().getOutMessage()
|| message == message.getExchange().getOutFaultMessage();
if (isOutbound) {
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
IOUtils.closeQuietly(cs);
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
csnew.flush();
IOUtils.closeQuietly(csnew);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Outbound message: " + currentEnvelopeMessage);
}
String res = changeOutboundMessage(currentEnvelopeMessage);
if (res != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Outbound message has been changed: " + res);
}
}
res = res != null ? res : currentEnvelopeMessage;
InputStream replaceInStream = IOUtils.toInputStream(res, "UTF-8");
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
os.flush();
message.setContent(OutputStream.class, os);
IOUtils.closeQuietly(os);
} catch (IOException ioe) {
getLogger().warn("Unable to perform change.", ioe);
throw new RuntimeException(ioe);
}
} else {
try {
InputStream is = message.getContent(InputStream.class);
String currentEnvelopeMessage = IOUtils.toString(is, "UTF-8");
IOUtils.closeQuietly(is);
if (getLogger().isDebugEnabled()) {
getLogger().debug("Inbound message: " + currentEnvelopeMessage);
}
String res = changeInboundMessage(currentEnvelopeMessage);
if (res != null) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("Inbound message has been changed: " + res);
}
}
res = res != null ? res : currentEnvelopeMessage;
is = IOUtils.toInputStream(res, "UTF-8");
message.setContent(InputStream.class, is);
IOUtils.closeQuietly(is);
} catch (IOException ioe) {
getLogger().warn("Unable to perform change.", ioe);
throw new RuntimeException(ioe);
}
}
}
public void handleFault(Message message) {
}
private class CachedStream extends CachedOutputStream {
public CachedStream() {
super();
}
protected void doFlush() throws IOException {
currentStream.flush();
}
protected void doClose() throws IOException {
}
protected void onWrite() throws IOException {
}
}
}
I had this problem as well today. After much weeping and gnashing of teeth, I was able to alter the StreamInterceptor class in the configuration_interceptor demo that comes with the CXF source:
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String soapMessage = IOUtils.toString(csnew.getInputStream());
...
The soapMessage variable will contain the complete SOAP message. You should be able to manipulate the soap message, flush it to an output stream and do a message.setContent(OutputStream.class... call to put your modifications on the message. This comes with no warranty, since I'm pretty new to CXF myself!
Note: CachedStream is a private class in the StreamInterceptor class. Don't forget to configure your interceptor to run in the PRE_STREAM phase so that the SOAP interceptors have a chance to write the SOAP message.
Following is able to bubble up server side exceptions. Use of os.close() instead of IOUtils.closeQuietly(os) in previous solution is also able to bubble up exceptions.
public class OutInterceptor extends AbstractPhaseInterceptor<Message> {
public OutInterceptor() {
super(Phase.PRE_STREAM);
addBefore(StaxOutInterceptor.class.getName());
}
public void handleMessage(Message message) {
OutputStream os = message.getContent(OutputStream.class);
CachedOutputStream cos = new CachedOutputStream();
message.setContent(OutputStream.class, cos);
message.getInterceptorChain.aad(new PDWSOutMessageChangingInterceptor(os));
}
}
public class OutMessageChangingInterceptor extends AbstractPhaseInterceptor<Message> {
private OutputStream os;
public OutMessageChangingInterceptor(OutputStream os){
super(Phase.PRE_STREAM_ENDING);
addAfter(StaxOutEndingInterceptor.class.getName());
this.os = os;
}
public void handleMessage(Message message) {
try {
CachedOutputStream csnew = (CachedOutputStream) message .getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString( csnew.getInputStream(), (String) message.get(Message.ENCODING));
csnew.flush();
IOUtils.closeQuietly(csnew);
String res = changeOutboundMessage(currentEnvelopeMessage);
res = res != null ? res : currentEnvelopeMessage;
InputStream replaceInStream = IOUtils.tolnputStream(res, (String) message.get(Message.ENCODING));
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
message.setContent(OutputStream.class, os);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}
Good example for replacing outbound soap content based on this
package kz.bee.bip;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor;
import org.apache.cxf.io.CachedOutputStream;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
public class SOAPOutboundInterceptor extends AbstractPhaseInterceptor<Message> {
public SOAPOutboundInterceptor() {
super(Phase.PRE_STREAM);
addBefore(SoapPreProtocolOutInterceptor.class.getName());
}
public void handleMessage(Message message) {
boolean isOutbound = false;
isOutbound = message == message.getExchange().getOutMessage()
|| message == message.getExchange().getOutFaultMessage();
if (isOutbound) {
OutputStream os = message.getContent(OutputStream.class);
CachedStream cs = new CachedStream();
message.setContent(OutputStream.class, cs);
message.getInterceptorChain().doIntercept(message);
try {
cs.flush();
IOUtils.closeQuietly(cs);
CachedOutputStream csnew = (CachedOutputStream) message.getContent(OutputStream.class);
String currentEnvelopeMessage = IOUtils.toString(csnew.getInputStream(), "UTF-8");
csnew.flush();
IOUtils.closeQuietly(csnew);
/* here we can set new data instead of currentEnvelopeMessage*/
InputStream replaceInStream = IOUtils.toInputStream(currentEnvelopeMessage, "UTF-8");
IOUtils.copy(replaceInStream, os);
replaceInStream.close();
IOUtils.closeQuietly(replaceInStream);
os.flush();
message.setContent(OutputStream.class, os);
IOUtils.closeQuietly(os);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
public void handleFault(Message message) {
}
private static class CachedStream extends CachedOutputStream {
public CachedStream() {
super();
}
protected void doFlush() throws IOException {
currentStream.flush();
}
protected void doClose() throws IOException {
}
protected void onWrite() throws IOException {
}
}
}
a better way would be to modify the message using the DOM interface, you need to add the SAAJOutInterceptor first (this might have a performance hit for big requests) and then your custom interceptor that is executed in phase USER_PROTOCOL
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.binding.soap.interceptor.AbstractSoapInterceptor;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Node;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
abstract public class SoapNodeModifierInterceptor extends AbstractSoapInterceptor {
SoapNodeModifierInterceptor() { super(Phase.USER_PROTOCOL); }
#Override public void handleMessage(SoapMessage message) throws Fault {
try {
if (message == null) {
return;
}
SOAPMessage sm = message.getContent(SOAPMessage.class);
if (sm == null) {
throw new RuntimeException("You must add the SAAJOutInterceptor to the chain");
}
modifyNodes(sm.getSOAPBody());
} catch (SOAPException e) {
throw new RuntimeException(e);
}
}
abstract void modifyNodes(Node node);
}
this one's working for me. It's based on StreamInterceptor class from configuration_interceptor example in Apache CXF samples.
It's in Scala instead of Java but the conversion is straightforward.
I tried to add comments to explain what's happening (as far as I understand).
import java.io.OutputStream
import org.apache.cxf.binding.soap.interceptor.SoapPreProtocolOutInterceptor
import org.apache.cxf.helpers.IOUtils
import org.apache.cxf.io.CachedOutputStream
import org.apache.cxf.message.Message
import org.apache.cxf.phase.AbstractPhaseInterceptor
import org.apache.cxf.phase.Phase
// java note: base constructor call is hidden at the end of class declaration
class StreamInterceptor() extends AbstractPhaseInterceptor[Message](Phase.PRE_STREAM) {
// java note: put this into the constructor after calling super(Phase.PRE_STREAM);
addBefore(classOf[SoapPreProtocolOutInterceptor].getName)
override def handleMessage(message: Message) = {
// get original output stream
val osOrig = message.getContent(classOf[OutputStream])
// our output stream
val osNew = new CachedOutputStream
// replace it with ours
message.setContent(classOf[OutputStream], osNew)
// fills the osNew instead of osOrig
message.getInterceptorChain.doIntercept(message)
// flush before getting content
osNew.flush()
// get filled content
val content = IOUtils.toString(osNew.getInputStream, "UTF-8")
// we got the content, we may close our output stream now
osNew.close()
// modified content
val modifiedContent = content.replace("a-string", "another-string")
// fill original output stream
osOrig.write(modifiedContent.getBytes("UTF-8"))
// flush before set
osOrig.flush()
// replace with original output stream filled with our modified content
message.setContent(classOf[OutputStream], osOrig)
}
}