Issue #2827 fixed collab dataserver returning empty body with 200

we check if a file exists in and out of the lock when reading
if the file is deleted between checks, the inner check attempts to return an error
we had already access the output stream from the response object so it is committed to 200
fixed by waiting to get the output stream until after the second check


Former-commit-id: bfde9c37ce [formerly bfde9c37ce [formerly 31c42d161146b0367435c66535d4da700a572f24]]
Former-commit-id: d44c55e976
Former-commit-id: d693c07bb4
This commit is contained in:
Brian Clements 2014-03-11 16:44:54 -05:00
parent 8e0947136f
commit 22d6302151
5 changed files with 168 additions and 177 deletions

View file

@ -13,6 +13,8 @@
<property name="tmp.bin.dir" value="${tmp.dir}/bin" /> <property name="tmp.bin.dir" value="${tmp.dir}/bin" />
<property name="tmp.lib.dir" value="${tmp.dir}/lib" /> <property name="tmp.lib.dir" value="${tmp.dir}/lib" />
<property name="compile.with.debug" value="on" />
<!-- public static final --> <!-- public static final -->
<path id="ant.classpath"> <path id="ant.classpath">
<fileset dir="${esb.build.directory}/lib/ant"> <fileset dir="${esb.build.directory}/lib/ant">
@ -26,8 +28,7 @@
<available file="${basedir}/tmp" type="dir" /> <available file="${basedir}/tmp" type="dir" />
<then> <then>
<delete includeemptydirs="true"> <delete includeemptydirs="true">
<fileset dir="${basedir}" <fileset dir="${basedir}" includes="tmp/**" />
includes="tmp/**" />
</delete> </delete>
</then> </then>
</if> </if>
@ -39,8 +40,7 @@
<available file="${dataserver.root.directory}/lib" /> <available file="${dataserver.root.directory}/lib" />
<then> <then>
<delete verbose="true" includeemptydirs="true"> <delete verbose="true" includeemptydirs="true">
<fileset dir="${dataserver.root.directory}/lib" <fileset dir="${dataserver.root.directory}/lib" includes="*/**" />
includes="*/**"/>
</delete> </delete>
</then> </then>
</if> </if>
@ -73,8 +73,7 @@
<available file="${includes.directory}" type="dir" /> <available file="${includes.directory}" type="dir" />
<then> <then>
<delete verbose="true" includeemptydirs="true"> <delete verbose="true" includeemptydirs="true">
<fileset dir="${includes.directory}" <fileset dir="${includes.directory}" includes="*/**" />
includes="*/**" />
</delete> </delete>
</then> </then>
</if> </if>
@ -83,32 +82,15 @@
<!-- run includegen --> <!-- run includegen -->
<echo message="Generating deployment list for feature: ${feature}" /> <echo message="Generating deployment list for feature: ${feature}" />
<includegen providerfilter="${includegen.filter}" <includegen providerfilter="${includegen.filter}" basedirectories="${basedirectories}" featurefile="${feature.file}" cotsout="${includes.directory}/cots.includes" plugsout="${includes.directory}/plugins.includes" coreout="${includes.directory}/core.includes" />
basedirectories="${basedirectories}"
featurefile="${feature.file}"
cotsout="${includes.directory}/cots.includes"
plugsout="${includes.directory}/plugins.includes"
coreout="${includes.directory}/core.includes" />
<!-- copy foss jars to tmp lib folder --> <!-- copy foss jars to tmp lib folder -->
<copyToBuild includes.file="${includes.directory}/cots.includes" <copyToBuild includes.file="${includes.directory}/cots.includes" source.base="${workspace}/cots" source.suffix="" includes.pattern="*.jar" target.dir="${tmp.lib.dir}" />
source.base="${workspace}/cots"
source.suffix=""
includes.pattern="*.jar"
target.dir="${tmp.lib.dir}" />
<!-- copy all uframe source to tmp src folder --> <!-- copy all uframe source to tmp src folder -->
<copyToBuild includes.file="${includes.directory}/core.includes" <copyToBuild includes.file="${includes.directory}/core.includes" source.base="${workspace}/edexOsgi" source.suffix="src" includes.pattern="**/*.java" target.dir="${tmp.src.dir}" />
source.base="${workspace}/edexOsgi"
source.suffix="src"
includes.pattern="**/*.java"
target.dir="${tmp.src.dir}" />
<copyToBuild includes.file="${includes.directory}/plugins.includes" <copyToBuild includes.file="${includes.directory}/plugins.includes" source.base="${workspace}/edexOsgi" source.suffix="src" includes.pattern="**/*.java" target.dir="${tmp.src.dir}" />
source.base="${workspace}/edexOsgi"
source.suffix="src"
includes.pattern="**/*.java"
target.dir="${tmp.src.dir}" />
<copy todir="${tmp.src.dir}" overwrite="true" verbose="true"> <copy todir="${tmp.src.dir}" overwrite="true" verbose="true">
<fileset dir="src" includes="**/*.java" /> <fileset dir="src" includes="**/*.java" />
@ -121,7 +103,7 @@
</fileset> </fileset>
</path> </path>
<mkdir dir="${tmp.bin.dir}" /> <mkdir dir="${tmp.bin.dir}" />
<javac destdir="${tmp.bin.dir}"> <javac destdir="${tmp.bin.dir}" debug="${compile.with.debug}">
<src path="${tmp.src.dir}" /> <src path="${tmp.src.dir}" />
<classpath refid="foss.path" /> <classpath refid="foss.path" />
</javac> </javac>
@ -138,10 +120,8 @@
<sequential> <sequential>
<echo>@{includes.file}</echo> <echo>@{includes.file}</echo>
<loadfile property="@{includes.file}.contents" <loadfile property="@{includes.file}.contents" srcfile="@{includes.file}" />
srcfile="@{includes.file}" /> <for param="line" list="${@{includes.file}.contents}" delimiter="${line.separator}">
<for param="line" list="${@{includes.file}.contents}"
delimiter="${line.separator}">
<sequential> <sequential>
<if> <if>
<available file="@{source.base}/@{line}/@{source.suffix}" type="dir" /> <available file="@{source.base}/@{line}/@{source.suffix}" type="dir" />
@ -160,9 +140,6 @@
</macrodef> </macrodef>
<!-- static --> <!-- static -->
<taskdef name="includegen" <taskdef name="includegen" classname="com.raytheon.uf.anttasks.includesgen.GenerateIncludesFromFeature" classpathref="ant.classpath" />
classname="com.raytheon.uf.anttasks.includesgen.GenerateIncludesFromFeature" <taskdef resource="net/sf/antcontrib/antlib.xml" classpath="${esb.build.directory}/lib/ant/ant-contrib-1.0b3.jar" />
classpathref="ant.classpath" />
<taskdef resource="net/sf/antcontrib/antlib.xml"
classpath="${esb.build.directory}/lib/ant/ant-contrib-1.0b3.jar" />
</project> </project>

View file

@ -23,7 +23,6 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -44,7 +43,8 @@ import com.raytheon.uf.common.http.AcceptHeaderValue;
* *
* Date Ticket# Engineer Description * Date Ticket# Engineer Description
* ------------ ---------- ----------- -------------------------- * ------------ ---------- ----------- --------------------------
* Feb 5, 2014 2756 bclement Initial creation * Feb 05, 2014 2756 bclement Initial creation
* Mar 11, 2014 2827 bclement pass response object to FileManager in doGet
* *
* </pre> * </pre>
* *
@ -113,18 +113,17 @@ public class DataService extends HttpServlet {
throw new RestException(HttpServletResponse.SC_NOT_FOUND, throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"No Such Resource: " + file.getAbsolutePath()); "No Such Resource: " + file.getAbsolutePath());
} }
ServletOutputStream out = resp.getOutputStream();
if (file.isDirectory()) { if (file.isDirectory()) {
if (acceptsXml(req)) { if (acceptsXml(req)) {
resp.setContentType(XML_CONTENT_TYPE); resp.setContentType(XML_CONTENT_TYPE);
manager.readDirectoryAsXml(file, out); manager.readDirectoryAsXml(file, resp);
} else { } else {
resp.setContentType(HTML_CONTENT_TYPE); resp.setContentType(HTML_CONTENT_TYPE);
manager.readDirectoryAsHtml(file, out); manager.readDirectoryAsHtml(file, resp);
} }
} else { } else {
resp.setContentType(BINARY_CONTENT_TYPE); resp.setContentType(BINARY_CONTENT_TYPE);
manager.readFile(file, out); manager.readFile(file, resp);
} }
} catch (IOException e) { } catch (IOException e) {
log.warn("Problem handling GET", e); log.warn("Problem handling GET", e);

View file

@ -47,6 +47,7 @@ import com.raytheon.collaboration.dataserver.auth.ServerAuthManager;
* ------------ ---------- ----------- -------------------------- * ------------ ---------- ----------- --------------------------
* Feb 14, 2014 2756 bclement Initial creation * Feb 14, 2014 2756 bclement Initial creation
* Feb 28, 2014 2756 bclement added authManager * Feb 28, 2014 2756 bclement added authManager
* Mar 11, 2014 2827 bclement disabled sessions on servlet context handler
* *
* </pre> * </pre>
* *
@ -72,7 +73,7 @@ public class WebServerRunner implements Runnable {
Config.PORT_DEFAULT)); Config.PORT_DEFAULT));
ServletContextHandler context = new ServletContextHandler( ServletContextHandler context = new ServletContextHandler(
ServletContextHandler.SESSIONS); ServletContextHandler.NO_SESSIONS);
context.setContextPath("/"); context.setContextPath("/");
server.setHandler(context); server.setHandler(context);
@ -84,8 +85,7 @@ public class WebServerRunner implements Runnable {
String datapath = Config.getPath(Config.DATAPATH_KEY, String datapath = Config.getPath(Config.DATAPATH_KEY,
Config.DATAPATH_DEFAULT); Config.DATAPATH_DEFAULT);
String pathspec = datapath String pathspec = datapath + "*";
+ "*";
context.addServlet(new ServletHolder(new DataService(base)), pathspec); context.addServlet(new ServletHolder(new DataService(base)), pathspec);
List<MethodAuthHandler> methods = Arrays.asList(new PutAuthHandler( List<MethodAuthHandler> methods = Arrays.asList(new PutAuthHandler(

View file

@ -50,6 +50,7 @@ import com.raytheon.uf.common.xmpp.iq.SecurityToggleProvider;
* Feb 14, 2014 2756 bclement Initial creation * Feb 14, 2014 2756 bclement Initial creation
* Feb 28, 2014 2756 bclement added custom IQ packet support * Feb 28, 2014 2756 bclement added custom IQ packet support
* Mar 04, 2014 2756 bclement added xmpp server retry * Mar 04, 2014 2756 bclement added xmpp server retry
* Mar 11, 2014 2827 bclement changed (dis)connect messages from debug to info
* *
* </pre> * </pre>
* *
@ -97,7 +98,7 @@ public class XmppServerConnection implements Runnable {
registerListeners(conn); registerListeners(conn);
this.conn.connect(); this.conn.connect();
this.conn.login(user, password); this.conn.login(user, password);
log.debug("Connected to XMPP server at address: " + xmppServerAddress); log.info("Connected to XMPP server at address: " + xmppServerAddress);
} }
/** /**
@ -188,7 +189,7 @@ public class XmppServerConnection implements Runnable {
* disconnect from XMPP server * disconnect from XMPP server
*/ */
public void disconnect() { public void disconnect() {
log.debug("Disconnecting from XMPP server"); log.info("Disconnecting from XMPP server");
conn.disconnect(); conn.disconnect();
} }

View file

@ -25,12 +25,12 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.raytheon.collaboration.dataserver.RestException; import com.raytheon.collaboration.dataserver.RestException;
@ -50,6 +50,7 @@ import com.raytheon.uf.common.util.concurrent.KeyLocker;
* ------------ ---------- ----------- -------------------------- * ------------ ---------- ----------- --------------------------
* Feb 6, 2014 2756 bclement Initial creation * Feb 6, 2014 2756 bclement Initial creation
* Feb 28, 2014 2756 bclement moved to storage package, made buffer size public * Feb 28, 2014 2756 bclement moved to storage package, made buffer size public
* Mar 11, 2014 2827 bclement read methods take servlet response object
* *
* </pre> * </pre>
* *
@ -213,33 +214,45 @@ public class FileManager {
} }
/** /**
* Output file to stream * Output file to servlet response
* *
* @param file * @param file
* @param out * @param resp
* @throws IOException * @throws IOException
* @throws RestException * @throws RestException
*/ */
public void readFile(File file, OutputStream out) throws IOException, public void readFile(File file, HttpServletResponse resp)
throws IOException,
RestException { RestException {
InputStream in = null; InputStream in = null;
List<UsedLock> locks = null; List<UsedLock> locks = null;
ServletOutputStream out = null;
try { try {
locks = getReadLocks(file); locks = getReadLocks(file);
if ( !file.exists()){ if ( !file.isFile()){
throw new RestException(HttpServletResponse.SC_NOT_FOUND, throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"No Such File: " + file.getAbsoluteFile()); "No Such File: " + file.getAbsoluteFile());
} }
in = new FileInputStream(file); in = new FileInputStream(file);
/*
* We have to wait until we are sure we can read the file before we
* get the outputstream. This is because the act of getting the
* output stream commits to using it with a 200 response. If we
* throw an error, we won't be able to use the response object to
* send an error and we will send a 200 with an empty body
*/
out = resp.getOutputStream();
copy(in, out); copy(in, out);
} finally { } finally {
unlock(locks); unlock(locks);
if (in != null) { if (in != null) {
in.close(); in.close();
} }
if (out != null) {
out.close(); out.close();
} }
} }
}
/** /**
* Get a list of read only file locks for each file from the base to the * Get a list of read only file locks for each file from the base to the
@ -267,25 +280,25 @@ public class FileManager {
} }
/** /**
* Output contents of directory to stream in XML format * Output contents of directory to response in XML format
* *
* @param directory * @param directory
* @param out * @param resp
* @throws IOException * @throws IOException
* @throws RestException * @throws RestException
*/ */
public void readDirectoryAsXml(File directory, OutputStream out) public void readDirectoryAsXml(File directory, HttpServletResponse resp)
throws IOException, RestException { throws IOException, RestException {
List<UsedLock> locks = null; List<UsedLock> locks = null;
Writer w = null; Writer w = null;
try { try {
locks = getReadLocks(directory); locks = getReadLocks(directory);
if (!directory.exists()) { if (!directory.isDirectory()) {
// someone else modified it while waiting for lock // someone else modified it while waiting for lock
throw new RestException(HttpServletResponse.SC_NOT_FOUND, throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"No Such Directory: " + directory.getAbsolutePath()); "No Such Directory: " + directory.getAbsolutePath());
} }
w = new OutputStreamWriter(out, "UTF-8"); w = resp.getWriter();
w.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); w.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
w.write("<Contents xmlns=\"urn:uf:viz:collaboration\">"); w.write("<Contents xmlns=\"urn:uf:viz:collaboration\">");
for (File f : directory.listFiles()) { for (File f : directory.listFiles()) {
@ -325,25 +338,25 @@ public class FileManager {
} }
/** /**
* Output contents of directory to stream in HTML format * Output contents of directory to response in HTML format
* *
* @param directory * @param directory
* @param out * @param resp
* @throws IOException * @throws IOException
* @throws RestException * @throws RestException
*/ */
public void readDirectoryAsHtml(File directory, OutputStream out) public void readDirectoryAsHtml(File directory, HttpServletResponse resp)
throws IOException, RestException { throws IOException, RestException {
List<UsedLock> locks = null; List<UsedLock> locks = null;
Writer w = null; Writer w = null;
try { try {
locks = getReadLocks(directory); locks = getReadLocks(directory);
if (!directory.exists()) { if (!directory.isDirectory()) {
// someone else modified it while waiting for lock // someone else modified it while waiting for lock
throw new RestException(HttpServletResponse.SC_NOT_FOUND, throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"No Such Directory: " + directory.getAbsolutePath()); "No Such Directory: " + directory.getAbsolutePath());
} }
w = new OutputStreamWriter(out, "UTF-8"); w = resp.getWriter();
w.write("<!DOCTYPE html>\n"); w.write("<!DOCTYPE html>\n");
w.write("<html><body>"); w.write("<html><body>");
for (File f : directory.listFiles()) { for (File f : directory.listFiles()) {
@ -379,7 +392,7 @@ public class FileManager {
} }
/** /**
* Copy bytes from input to output * Copy bytes from input to output. Flushes output after writing.
* *
* @param in * @param in
* @param out * @param out
@ -392,6 +405,7 @@ public class FileManager {
while ((len = in.read(buff)) != -1) { while ((len = in.read(buff)) != -1) {
out.write(buff, 0, len); out.write(buff, 0, len);
} }
out.flush();
} }
/** /**