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: d44c55e976 [formerly bfde9c37ce [formerly 31c42d161146b0367435c66535d4da700a572f24]]
Former-commit-id: bfde9c37ce
Former-commit-id: ce1038723d
This commit is contained in:
Brian Clements 2014-03-11 16:44:54 -05:00
parent 2c4b520080
commit 2aa9dcb248
5 changed files with 168 additions and 177 deletions

View file

@ -1,168 +1,145 @@
<project default="deploy" basedir=".">
<property name="workspace" value="${basedir}/../.." />
<property name="esb.build.directory" value="${workspace}/edexOsgi/build.edex" />
<property name="feature.file" value="${basedir}.feature/feature.xml"/>
<property name="dataserver.root.directory" value="/awips2/collab-dataserver" />
<property name="includegen.filter" value="raytheon|collaboration.dataserver" />
<property name="basedirectories" value="${workspace}/edexOsgi;${workspace}/javaUtilities;${workspace}/cots" />
<property name="workspace" value="${basedir}/../.." />
<property name="esb.build.directory" value="${workspace}/edexOsgi/build.edex" />
<property name="feature.file" value="${basedir}.feature/feature.xml" />
<property name="dataserver.root.directory" value="/awips2/collab-dataserver" />
<property name="includegen.filter" value="raytheon|collaboration.dataserver" />
<property name="basedirectories" value="${workspace}/edexOsgi;${workspace}/javaUtilities;${workspace}/cots" />
<property name="tmp.dir" value="${basedir}/tmp" />
<property name="includes.directory" value="${tmp.dir}/includes" />
<property name="tmp.src.dir" value="${tmp.dir}/src" />
<property name="tmp.bin.dir" value="${tmp.dir}/bin" />
<property name="tmp.lib.dir" value="${tmp.dir}/lib" />
<property name="tmp.dir" value="${basedir}/tmp" />
<property name="includes.directory" value="${tmp.dir}/includes" />
<property name="tmp.src.dir" value="${tmp.dir}/src" />
<property name="tmp.bin.dir" value="${tmp.dir}/bin" />
<property name="tmp.lib.dir" value="${tmp.dir}/lib" />
<!-- public static final -->
<path id="ant.classpath">
<fileset dir="${esb.build.directory}/lib/ant">
<include name="*.jar" />
</fileset>
</path>
<property name="compile.with.debug" value="on" />
<target name="clean" >
<!-- cleanup the temporary directories -->
<if>
<available file="${basedir}/tmp" type="dir" />
<then>
<delete includeemptydirs="true">
<fileset dir="${basedir}"
includes="tmp/**" />
</delete>
</then>
</if>
</target>
<!-- public static final -->
<path id="ant.classpath">
<fileset dir="${esb.build.directory}/lib/ant">
<include name="*.jar" />
</fileset>
</path>
<target name="clean-dest">
<!-- clean up the runtime directories -->
<if>
<available file="${dataserver.root.directory}/lib" />
<then>
<delete verbose="true" includeemptydirs="true">
<fileset dir="${dataserver.root.directory}/lib"
includes="*/**"/>
</delete>
</then>
</if>
</target>
<target name="clean">
<!-- cleanup the temporary directories -->
<if>
<available file="${basedir}/tmp" type="dir" />
<then>
<delete includeemptydirs="true">
<fileset dir="${basedir}" includes="tmp/**" />
</delete>
</then>
</if>
</target>
<target name="deploy" depends="clean-dest,build" >
<mkdir dir="${dataserver.root.directory}/lib/uframe"/>
<!-- jar up dataserver -->
<jar destfile="${dataserver.root.directory}/lib/uframe/collaboration.dataserver.jar" basedir="${tmp.bin.dir}"/>
<!-- deploy foss -->
<copy todir="${dataserver.root.directory}/lib/foss" overwrite="true" verbose="true">
<fileset dir="${tmp.lib.dir}" includes="*.jar"/>
</copy>
<!-- deploy scripts and config -->
<copy todir="${dataserver.root.directory}/config" overwrite="false" verbose="true">
<fileset dir="${basedir}/config" />
</copy>
<copy todir="${dataserver.root.directory}/bin" overwrite="true" verbose="true">
<fileset dir="${basedir}/scriptBin" />
</copy>
<chmod dir="${dataserver.root.directory}/bin" perm="ug+rx" includes="**/*.sh"/>
<!-- clean up -->
<antcall target="clean"/>
</target>
<target name="clean-dest">
<!-- clean up the runtime directories -->
<if>
<available file="${dataserver.root.directory}/lib" />
<then>
<delete verbose="true" includeemptydirs="true">
<fileset dir="${dataserver.root.directory}/lib" includes="*/**" />
</delete>
</then>
</if>
</target>
<target name="build" >
<sequential>
<!-- prepare to run includegen -->
<if>
<available file="${includes.directory}" type="dir" />
<then>
<delete verbose="true" includeemptydirs="true">
<fileset dir="${includes.directory}"
includes="*/**" />
</delete>
</then>
</if>
<mkdir dir="${includes.directory}" />
<target name="deploy" depends="clean-dest,build">
<mkdir dir="${dataserver.root.directory}/lib/uframe" />
<!-- jar up dataserver -->
<jar destfile="${dataserver.root.directory}/lib/uframe/collaboration.dataserver.jar" basedir="${tmp.bin.dir}" />
<!-- deploy foss -->
<copy todir="${dataserver.root.directory}/lib/foss" overwrite="true" verbose="true">
<fileset dir="${tmp.lib.dir}" includes="*.jar" />
</copy>
<!-- deploy scripts and config -->
<copy todir="${dataserver.root.directory}/config" overwrite="false" verbose="true">
<fileset dir="${basedir}/config" />
</copy>
<copy todir="${dataserver.root.directory}/bin" overwrite="true" verbose="true">
<fileset dir="${basedir}/scriptBin" />
</copy>
<chmod dir="${dataserver.root.directory}/bin" perm="ug+rx" includes="**/*.sh" />
<!-- clean up -->
<antcall target="clean" />
</target>
<!-- run includegen -->
<echo message="Generating deployment list for feature: ${feature}" />
<target name="build">
<sequential>
<!-- prepare to run includegen -->
<if>
<available file="${includes.directory}" type="dir" />
<then>
<delete verbose="true" includeemptydirs="true">
<fileset dir="${includes.directory}" includes="*/**" />
</delete>
</then>
</if>
<mkdir dir="${includes.directory}" />
<includegen providerfilter="${includegen.filter}"
basedirectories="${basedirectories}"
featurefile="${feature.file}"
cotsout="${includes.directory}/cots.includes"
plugsout="${includes.directory}/plugins.includes"
coreout="${includes.directory}/core.includes" />
<!-- run includegen -->
<echo message="Generating deployment list for feature: ${feature}" />
<!-- copy foss jars to tmp lib folder -->
<copyToBuild includes.file="${includes.directory}/cots.includes"
source.base="${workspace}/cots"
source.suffix=""
includes.pattern="*.jar"
target.dir="${tmp.lib.dir}" />
<includegen providerfilter="${includegen.filter}" basedirectories="${basedirectories}" featurefile="${feature.file}" cotsout="${includes.directory}/cots.includes" plugsout="${includes.directory}/plugins.includes" coreout="${includes.directory}/core.includes" />
<!-- copy all uframe source to tmp src folder -->
<copyToBuild includes.file="${includes.directory}/core.includes"
source.base="${workspace}/edexOsgi"
source.suffix="src"
includes.pattern="**/*.java"
target.dir="${tmp.src.dir}" />
<!-- copy foss jars to tmp lib folder -->
<copyToBuild includes.file="${includes.directory}/cots.includes" source.base="${workspace}/cots" source.suffix="" includes.pattern="*.jar" target.dir="${tmp.lib.dir}" />
<copyToBuild includes.file="${includes.directory}/plugins.includes"
source.base="${workspace}/edexOsgi"
source.suffix="src"
includes.pattern="**/*.java"
target.dir="${tmp.src.dir}" />
<!-- copy all uframe source to tmp src folder -->
<copyToBuild includes.file="${includes.directory}/core.includes" source.base="${workspace}/edexOsgi" source.suffix="src" includes.pattern="**/*.java" target.dir="${tmp.src.dir}" />
<copy todir="${tmp.src.dir}" overwrite="true" verbose="true">
<fileset dir="src" includes="**/*.java" />
</copy>
<copyToBuild includes.file="${includes.directory}/plugins.includes" source.base="${workspace}/edexOsgi" source.suffix="src" includes.pattern="**/*.java" target.dir="${tmp.src.dir}" />
<!-- compile all uframe source at once -->
<path id="foss.path">
<fileset dir="${tmp.lib.dir}">
<include name="*.jar"/>
</fileset>
</path>
<mkdir dir="${tmp.bin.dir}" />
<javac destdir="${tmp.bin.dir}">
<src path="${tmp.src.dir}"/>
<classpath refid="foss.path"/>
</javac>
</sequential>
</target>
<copy todir="${tmp.src.dir}" overwrite="true" verbose="true">
<fileset dir="src" includes="**/*.java" />
</copy>
<!-- iterates over includes file and copies files -->
<macrodef name="copyToBuild">
<attribute name="includes.file"/>
<attribute name="source.base" />
<attribute name="source.suffix" />
<attribute name="includes.pattern" />
<attribute name="target.dir"/>
<!-- compile all uframe source at once -->
<path id="foss.path">
<fileset dir="${tmp.lib.dir}">
<include name="*.jar" />
</fileset>
</path>
<mkdir dir="${tmp.bin.dir}" />
<javac destdir="${tmp.bin.dir}" debug="${compile.with.debug}">
<src path="${tmp.src.dir}" />
<classpath refid="foss.path" />
</javac>
</sequential>
</target>
<sequential>
<echo>@{includes.file}</echo>
<loadfile property="@{includes.file}.contents"
srcfile="@{includes.file}" />
<for param="line" list="${@{includes.file}.contents}"
delimiter="${line.separator}">
<sequential>
<if>
<available file="@{source.base}/@{line}/@{source.suffix}" type="dir" />
<then>
<copy todir="@{target.dir}" overwrite="true" verbose="true">
<fileset dir="@{source.base}/@{line}/@{source.suffix}" includes="@{includes.pattern}" />
</copy>
</then>
<else>
<echo>No files found at @{source.base}/@{line}/@{source.suffix}</echo>
</else>
</if>
</sequential>
</for>
</sequential>
</macrodef>
<!-- iterates over includes file and copies files -->
<macrodef name="copyToBuild">
<attribute name="includes.file" />
<attribute name="source.base" />
<attribute name="source.suffix" />
<attribute name="includes.pattern" />
<attribute name="target.dir" />
<!-- static -->
<taskdef name="includegen"
classname="com.raytheon.uf.anttasks.includesgen.GenerateIncludesFromFeature"
classpathref="ant.classpath" />
<taskdef resource="net/sf/antcontrib/antlib.xml"
classpath="${esb.build.directory}/lib/ant/ant-contrib-1.0b3.jar" />
<sequential>
<echo>@{includes.file}</echo>
<loadfile property="@{includes.file}.contents" srcfile="@{includes.file}" />
<for param="line" list="${@{includes.file}.contents}" delimiter="${line.separator}">
<sequential>
<if>
<available file="@{source.base}/@{line}/@{source.suffix}" type="dir" />
<then>
<copy todir="@{target.dir}" overwrite="true" verbose="true">
<fileset dir="@{source.base}/@{line}/@{source.suffix}" includes="@{includes.pattern}" />
</copy>
</then>
<else>
<echo>No files found at @{source.base}/@{line}/@{source.suffix}</echo>
</else>
</if>
</sequential>
</for>
</sequential>
</macrodef>
<!-- static -->
<taskdef name="includegen" classname="com.raytheon.uf.anttasks.includesgen.GenerateIncludesFromFeature" classpathref="ant.classpath" />
<taskdef resource="net/sf/antcontrib/antlib.xml" classpath="${esb.build.directory}/lib/ant/ant-contrib-1.0b3.jar" />
</project>

View file

@ -23,7 +23,6 @@ import java.io.File;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -44,7 +43,8 @@ import com.raytheon.uf.common.http.AcceptHeaderValue;
*
* 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>
*
@ -113,18 +113,17 @@ public class DataService extends HttpServlet {
throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"No Such Resource: " + file.getAbsolutePath());
}
ServletOutputStream out = resp.getOutputStream();
if (file.isDirectory()) {
if (acceptsXml(req)) {
resp.setContentType(XML_CONTENT_TYPE);
manager.readDirectoryAsXml(file, out);
manager.readDirectoryAsXml(file, resp);
} else {
resp.setContentType(HTML_CONTENT_TYPE);
manager.readDirectoryAsHtml(file, out);
manager.readDirectoryAsHtml(file, resp);
}
} else {
resp.setContentType(BINARY_CONTENT_TYPE);
manager.readFile(file, out);
manager.readFile(file, resp);
}
} catch (IOException 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 28, 2014 2756 bclement added authManager
* Mar 11, 2014 2827 bclement disabled sessions on servlet context handler
*
* </pre>
*
@ -72,7 +73,7 @@ public class WebServerRunner implements Runnable {
Config.PORT_DEFAULT));
ServletContextHandler context = new ServletContextHandler(
ServletContextHandler.SESSIONS);
ServletContextHandler.NO_SESSIONS);
context.setContextPath("/");
server.setHandler(context);
@ -84,8 +85,7 @@ public class WebServerRunner implements Runnable {
String datapath = Config.getPath(Config.DATAPATH_KEY,
Config.DATAPATH_DEFAULT);
String pathspec = datapath
+ "*";
String pathspec = datapath + "*";
context.addServlet(new ServletHolder(new DataService(base)), pathspec);
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 28, 2014 2756 bclement added custom IQ packet support
* Mar 04, 2014 2756 bclement added xmpp server retry
* Mar 11, 2014 2827 bclement changed (dis)connect messages from debug to info
*
* </pre>
*
@ -97,7 +98,7 @@ public class XmppServerConnection implements Runnable {
registerListeners(conn);
this.conn.connect();
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
*/
public void disconnect() {
log.debug("Disconnecting from XMPP server");
log.info("Disconnecting from XMPP server");
conn.disconnect();
}

View file

@ -25,12 +25,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
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 28, 2014 2756 bclement moved to storage package, made buffer size public
* Mar 11, 2014 2827 bclement read methods take servlet response object
*
* </pre>
*
@ -213,31 +214,43 @@ public class FileManager {
}
/**
* Output file to stream
* Output file to servlet response
*
* @param file
* @param out
* @param resp
* @throws IOException
* @throws RestException
*/
public void readFile(File file, OutputStream out) throws IOException,
public void readFile(File file, HttpServletResponse resp)
throws IOException,
RestException {
InputStream in = null;
List<UsedLock> locks = null;
ServletOutputStream out = null;
try {
locks = getReadLocks(file);
if ( !file.exists()){
if ( !file.isFile()){
throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"No Such File: " + file.getAbsoluteFile());
}
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);
} finally {
unlock(locks);
if (in != null) {
in.close();
}
out.close();
if (out != null) {
out.close();
}
}
}
@ -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 out
* @param resp
* @throws IOException
* @throws RestException
*/
public void readDirectoryAsXml(File directory, OutputStream out)
public void readDirectoryAsXml(File directory, HttpServletResponse resp)
throws IOException, RestException {
List<UsedLock> locks = null;
Writer w = null;
try {
locks = getReadLocks(directory);
if (!directory.exists()) {
if (!directory.isDirectory()) {
// someone else modified it while waiting for lock
throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"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("<Contents xmlns=\"urn:uf:viz:collaboration\">");
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 out
* @param resp
* @throws IOException
* @throws RestException
*/
public void readDirectoryAsHtml(File directory, OutputStream out)
public void readDirectoryAsHtml(File directory, HttpServletResponse resp)
throws IOException, RestException {
List<UsedLock> locks = null;
Writer w = null;
try {
locks = getReadLocks(directory);
if (!directory.exists()) {
if (!directory.isDirectory()) {
// someone else modified it while waiting for lock
throw new RestException(HttpServletResponse.SC_NOT_FOUND,
"No Such Directory: " + directory.getAbsolutePath());
}
w = new OutputStreamWriter(out, "UTF-8");
w = resp.getWriter();
w.write("<!DOCTYPE html>\n");
w.write("<html><body>");
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 out
@ -392,6 +405,7 @@ public class FileManager {
while ((len = in.read(buff)) != -1) {
out.write(buff, 0, len);
}
out.flush();
}
/**