/* * Copyright 1998-2014 University Corporation for Atmospheric Research/Unidata * * Portions of this software were developed by the Unidata Program at the * University Corporation for Atmospheric Research. * * Access and use of this software shall impose the following obligations * and understandings on the user. The user is granted the right, without * any fee or cost, to use, copy, modify, alter, enhance and distribute * this software, and any derivative works thereof, and its supporting * documentation for any purpose whatsoever, provided that this entire * notice appears in all copies of the software, derivative works and * supporting documentation. Further, UCAR requests that the user credit * UCAR/Unidata in any publications that result from the use of this * software or in any product that includes this software. The names UCAR * and/or Unidata, however, may not be used in any advertising or publicity * to endorse or promote any products or commercial entity unless specific * written permission is obtained from UCAR/Unidata. The user also * understands that UCAR/Unidata is not obligated to provide the user with * any support, consulting, training or assistance of any kind with regard * to the use, operation and performance of this software nor to provide * the user with any updates, revisions, new versions or "bug fixes." * * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. */ package thredds.server.opendap; import opendap.dap.*; import opendap.servers.*; import opendap.dap.parsers.ParseException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.*; import java.util.*; import java.util.zip.DeflaterOutputStream; import java.net.URI; import opendap.servlet.*; import opendap.servlet.AbstractServlet; import thredds.server.admin.DebugController; import thredds.servlet.*; import thredds.servlet.filter.CookieFilter; import ucar.ma2.DataType; import ucar.ma2.Range; import ucar.ma2.Section; import ucar.nc2.constants.CDM; import ucar.nc2.dods.DODSNetcdfFile; import ucar.nc2.NetcdfFile; import ucar.nc2.util.EscapeStrings; /** * THREDDS opendap server. * * @author jcaron * @author Nathan David Potter * @since Apr 27, 2009 (branched) */ public class OpendapServlet extends AbstractServlet { static public org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OpendapServlet.class); private boolean allowSessions = false; private boolean allowDeflate = false; // handled by Tomcat private String odapVersionString = "opendap/3.7"; private URI baseURI = null; private int ascLimit = 50; private int binLimit = 500; private boolean debugSession = false; public void init() throws javax.servlet.ServletException { super.init(); org.slf4j.Logger logServerStartup = org.slf4j.LoggerFactory.getLogger("serverStartup"); logServerStartup.info(getClass().getName() + " initialization start"); this.ascLimit = ThreddsConfig.getInt("Opendap.ascLimit", ascLimit); this.binLimit = ThreddsConfig.getInt("Opendap.binLimit", binLimit); this.odapVersionString = ThreddsConfig.get("Opendap.serverVersion", odapVersionString); logServerStartup.info(getClass().getName() + " version= " + odapVersionString + " ascLimit = " + ascLimit + " binLimit = " + binLimit); // debugging actions makeDebugActions(); logServerStartup.info(getClass().getName() + " initialization done"); } public String getServerVersion() { return this.odapVersionString; } // Servlets that support HTTP GET requests and can quickly determine their last modification time should // override this method. This makes browser and proxy caches work more effectively, reducing the load on // server and network resources. protected long getLastModified(HttpServletRequest req) { String query = req.getQueryString(); if (query != null) return -1; String path = req.getPathInfo(); if (path == null) return -1; if (path.endsWith(".asc")) path = path.substring(0, path.length() - 4); else if (path.endsWith(".ascii")) path = path.substring(0, path.length() - 6); else if (path.endsWith(".das")) path = path.substring(0, path.length() - 4); else if (path.endsWith(".dds")) path = path.substring(0, path.length() - 4); else if (path.endsWith(".ddx")) path = path.substring(0, path.length() - 4); else if (path.endsWith(".dods")) path = path.substring(0, path.length() - 5); else if (path.endsWith(".html")) path = path.substring(0, path.length() - 5); else if (path.endsWith(".info")) path = path.substring(0, path.length() - 5); else if (path.endsWith(".opendap")) path = path.substring(0, path.length() - 5); else return -1; // if (null != DatasetHandler.findResourceControl( path)) return -1; // LOOK weird Firefox behaviour? File file = DataRootHandler.getInstance().getCrawlableDatasetAsFile(path); if ((file != null) && file.exists()) return file.lastModified(); return -1; } ///////////////////////////////////////////////////////////////////////////// public void doGet(HttpServletRequest request, HttpServletResponse response) { log.debug("doGet(): User-Agent = " + request.getHeader("User-Agent")); String path = null; ReqState rs = getRequestState(request, response); try { path = request.getPathInfo(); log.debug("doGet path={}", path); if (thredds.servlet.Debug.isSet("showRequestDetail")) log.debug(ServletUtil.showRequestDetail(this, request)); if (path == null) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } if (baseURI == null) { // first time, set baseURI URI reqURI = ServletUtil.getRequestURI(request); // Build base URI from request (rather than hard-coding "/thredds/dodsC/"). String baseUriString = request.getContextPath() + request.getServletPath() + "/"; baseURI = reqURI.resolve(baseUriString); log.debug("doGet(): baseURI was set = {}", baseURI); } /* if (path.endsWith("latest.xml")) { // LOOK: used ?? DataRootHandler.getInstance().processReqForLatestDataset(this, request, response); return; } */ // Redirect all catalog requests at the root level. if (path.equals("/") || path.equals("/catalog.html") || path.equals("/catalog.xml")) { ServletUtil.sendPermanentRedirect(ServletUtil.getContextPath() + path, request, response); return; } /* Make sure catalog requests match a dataRoot before trying to handle. LOOK: used? if (path.endsWith("/") || path.endsWith("/catalog.html") || path.endsWith("/catalog.xml")) { if (!DataRootHandler.getInstance().hasDataRootMatch(path)) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } if (!DataRootHandler.getInstance().processReqForCatalog(request, response)) log.error("catalog request failed "); return; } */ if (rs != null) { String dataSet = rs.getDataSet(); String requestSuffix = rs.getRequestSuffix(); if ((dataSet == null) || dataSet.equals("/") || dataSet.equals("")) { doGetDIR(rs); } else if (requestSuffix.equalsIgnoreCase("blob")) { doGetBLOB(rs); } else if (requestSuffix.equalsIgnoreCase("close")) { doClose(rs); } else if (requestSuffix.equalsIgnoreCase("dds")) { doGetDDS(rs); } else if (requestSuffix.equalsIgnoreCase("das")) { doGetDAS(rs); } else if (requestSuffix.equalsIgnoreCase("ddx")) { doGetDDX(rs); } else if (requestSuffix.equalsIgnoreCase("dods")) { doGetDAP2Data(rs); } else if (requestSuffix.equalsIgnoreCase("asc") || requestSuffix.equalsIgnoreCase("ascii")) { doGetASC(rs); } else if (requestSuffix.equalsIgnoreCase("info")) { doGetINFO(rs); } else if (requestSuffix.equalsIgnoreCase("html") || requestSuffix.equalsIgnoreCase("htm")) { doGetHTML(rs); } else if (requestSuffix.equalsIgnoreCase("ver") || requestSuffix.equalsIgnoreCase("version") || dataSet.equalsIgnoreCase("/version") || dataSet.equalsIgnoreCase("/version/")) { doGetVER(rs); } else if (dataSet.equalsIgnoreCase("/help") || dataSet.equalsIgnoreCase("/help/") || dataSet.equalsIgnoreCase("/" + requestSuffix) || requestSuffix.equalsIgnoreCase("help")) { doGetHELP(rs); } else { sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, "Unrecognized request"); return; } } else { sendErrorResponse(response, HttpServletResponse.SC_BAD_REQUEST, "Unrecognized request"); return; } // plain ol' 404 } catch (FileNotFoundException e) { // e.printStackTrace(); sendErrorResponse(response, HttpServletResponse.SC_NOT_FOUND, e.getMessage()); // DAP2Exception bad url } catch (BadURLException e) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); dap2ExceptionHandler(e, rs); // all other DAP2Exception } catch (DAP2Exception de) { int status = (de.getErrorCode() == DAP2Exception.NO_SUCH_FILE) ? HttpServletResponse.SC_NOT_FOUND : HttpServletResponse.SC_BAD_REQUEST; if ((de.getErrorCode() == DAP2Exception.UNKNOWN_ERROR) || ((de.getErrorCode() == DAP2Exception.UNDEFINED_ERROR)) && (de.getErrorMessage() != null)) log.info(de.getErrorMessage()); response.setStatus(status); dap2ExceptionHandler(de, rs); // parsing, usually the CE } catch (ParseException pe) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); parseExceptionHandler(pe, response); // 403 - request too big } catch (UnsupportedOperationException e) { sendErrorResponse(response, HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } catch (java.net.SocketException e) { log.info("SocketException: " + e.getMessage(), e); } catch (IOException e) { String eName = e.getClass().getName(); // dont want compile time dependency on ClientAbortException if (eName.equals("org.apache.catalina.connector.ClientAbortException")) { log.debug("ClientAbortException: " + e.getMessage()); return; } log.error("path= " + path, e); sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); // everything else } catch (Throwable t) { log.error("path2= " + path, t); t.printStackTrace(); // LOOK, logger not showing stack trace sendErrorResponse(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage()); } } public void doGetASC(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); GuardedDataset ds = null; try { ds = getDataset(rs); if (ds == null) return; response.setHeader("XDODS-Server", getServerVersion()); response.setContentType("text/plain"); response.setHeader("Content-Description", "dods-ascii"); log.debug("Sending OPeNDAP ASCII Data For: " + rs + " CE: '" + rs.getConstraintExpression() + "'"); ServerDDS dds = ds.getDDS(); CEEvaluator ce = new CEEvaluator(dds); ce.parseConstraint(rs); checkSize(dds, true); PrintWriter pw = new PrintWriter(response.getOutputStream()); dds.printConstrained(pw); pw.println("---------------------------------------------"); AsciiWriter writer = new AsciiWriter(); // could be static writer.toASCII(pw, dds, ds); // the way that getDAP2Data works // DataOutputStream sink = new DataOutputStream(bOut); // ce.send(myDDS.getName(), sink, ds); pw.flush(); } finally { // release lock if needed if (ds != null) ds.release(); } } public void doGetDAS(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); GuardedDataset ds = null; try { ds = getDataset(rs); if (ds == null) return; response.setContentType("text/plain"); response.setHeader("XDODS-Server", getServerVersion()); response.setHeader("Content-Description", "dods-das"); OutputStream Out = new BufferedOutputStream(response.getOutputStream()); DAS myDAS = ds.getDAS(); myDAS.print(Out); } finally { // release lock if needed if (ds != null) ds.release(); } } public void doGetDDS(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); GuardedDataset ds = null; try { ds = getDataset(rs); if (null == ds) return; response.setContentType("text/plain"); response.setHeader("XDODS-Server", getServerVersion()); response.setHeader("Content-Description", "dods-dds"); OutputStream out = new BufferedOutputStream(response.getOutputStream()); ServerDDS myDDS = ds.getDDS(); if (rs.getConstraintExpression().equals("")) { // No Constraint Expression? // Send the whole DDS myDDS.print(out); out.flush(); } else { // Otherwise, send the constrained DDS // Instantiate the CEEvaluator and parse the constraint expression CEEvaluator ce = new CEEvaluator(myDDS); ce.parseConstraint(rs); // Send the constrained DDS back to the client PrintWriter pw = new PrintWriter(new OutputStreamWriter(out)); myDDS.printConstrained(pw); pw.flush(); } } finally { // release lock if needed if (ds != null) ds.release(); } } public void doGetDDX(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); GuardedDataset ds = null; try { ds = getDataset(rs); if (null == ds) return; response.setContentType("text/plain"); response.setHeader("XDODS-Server", getServerVersion()); response.setHeader("Content-Description", "dods-ddx"); OutputStream out = new BufferedOutputStream(response.getOutputStream()); ServerDDS myDDS = ds.getDDS(); myDDS.ingestDAS(ds.getDAS()); if (rs.getConstraintExpression().equals("")) { // No Constraint Expression? // Send the whole DDS myDDS.printXML(out); out.flush(); } else { // Otherwise, send the constrained DDS // Instantiate the CEEvaluator and parse the constraint expression CEEvaluator ce = new CEEvaluator(myDDS); ce.parseConstraint(rs); // Send the constrained DDS back to the client PrintWriter pw = new PrintWriter(new OutputStreamWriter(out)); myDDS.printConstrainedXML(pw); pw.flush(); } } finally { // release lock if needed if (ds != null) ds.release(); } } public void doGetBLOB(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); GuardedDataset ds = null; try { ds = getDataset(rs); if (null == ds) return; response.setContentType("application/octet-stream"); response.setHeader("XDODS-Server", getServerVersion()); response.setHeader("Content-Description", "dods-blob"); ServletOutputStream sOut = response.getOutputStream(); OutputStream bOut; DeflaterOutputStream dOut = null; if (rs.getAcceptsCompressed() && allowDeflate) { response.setHeader("Content-Encoding", "deflate"); dOut = new DeflaterOutputStream(sOut); bOut = new BufferedOutputStream(dOut); } else { bOut = new BufferedOutputStream(sOut); } ServerDDS myDDS = ds.getDDS(); CEEvaluator ce = new CEEvaluator(myDDS); ce.parseConstraint(rs); checkSize(myDDS, false); // Send the binary data back to the client DataOutputStream sink = new DataOutputStream(bOut); ce.send(myDDS.getEncodedName(), sink, ds); sink.flush(); // Finish up sending the compressed stuff, but don't // close the stream (who knows what the Servlet may expect!) if (null != dOut) dOut.finish(); bOut.flush(); } finally { // release lock if needed if (ds != null) ds.release(); } } private void doClose(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); HttpServletRequest request = rs.getRequest(); String reqPath = rs.getDataSet(); HttpSession session = request.getSession(); session.removeAttribute(reqPath); // work done in the listener response.setHeader("XDODS-Server", getServerVersion()); // needed by client /* if (path.endsWith(".close")) { closeSession(request, response); response.setContentLength(0); return; } // so we need to worry about deleting sessions? session.invalidate(); */ } public void doGetDAP2Data(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); GuardedDataset ds = null; try { ds = getDataset(rs); if (null == ds) return; response.setContentType("application/octet-stream"); response.setHeader("XDODS-Server", getServerVersion()); response.setHeader("Content-Description", "dods-data"); ServletOutputStream sOut = response.getOutputStream(); OutputStream bOut; DeflaterOutputStream dOut = null; if (rs.getAcceptsCompressed() && allowDeflate) { response.setHeader("Content-Encoding", "deflate"); dOut = new DeflaterOutputStream(sOut); bOut = new BufferedOutputStream(dOut); } else { bOut = new BufferedOutputStream(sOut); } ServerDDS myDDS = ds.getDDS(); CEEvaluator ce = new CEEvaluator(myDDS); ce.parseConstraint(rs); checkSize(myDDS, false); // Send the constrained DDS back to the client PrintWriter pw = new PrintWriter(new OutputStreamWriter(bOut)); myDDS.printConstrained(pw); // Send the Data delimiter back to the client pw.flush(); bOut.write("\nData:\n".getBytes(CDM.utf8Charset)); bOut.flush(); // Send the binary data back to the client DataOutputStream sink = new DataOutputStream(bOut); ce.send(myDDS.getEncodedName(), sink, ds); sink.flush(); // Finish up sending the compressed stuff, but don't // close the stream (who knows what the Servlet may expect!) if (null != dOut) dOut.finish(); bOut.flush(); } finally { // release lock if needed if (ds != null) ds.release(); } } public void doGetVER(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); response.setContentType("text/plain"); response.setHeader("XDODS-Server", getServerVersion()); response.setHeader("Content-Description", "dods-version"); PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream())); pw.println("Server Version: " + getServerVersion()); pw.flush(); } public void doGetHELP(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); response.setContentType("text/html"); response.setHeader("XDODS-Server", getServerVersion()); response.setHeader("Content-Description", "dods-help"); PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream())); printHelpPage(pw); pw.flush(); } public void doGetDIR(ReqState rs) throws Exception { // rather dangerous here, since you can go into an infinite loop // so we're going to insist that there's no suffix HttpServletResponse response = rs.getResponse(); HttpServletRequest request = rs.getRequest(); if ((rs.getRequestSuffix() == null) || (rs.getRequestSuffix().length() == 0)) { ServletUtil.forwardToCatalogServices(request, response); return; } sendErrorResponse(response, 0, "Unrecognized request"); } public void doGetINFO(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); GuardedDataset ds = null; try { ds = getDataset(rs); if (null == ds) return; PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream(),Util.UTF8)); response.setHeader("XDODS-Server", getServerVersion()); response.setContentType("text/html"); response.setHeader("Content-Description", "dods-description"); GetInfoHandler di = new GetInfoHandler(); di.sendINFO(pw, ds, rs); } finally { // release lock if needed if (ds != null) ds.release(); } } public void doGetHTML(ReqState rs) throws Exception { HttpServletResponse response = rs.getResponse(); HttpServletRequest request = rs.getRequest(); GuardedDataset ds = null; try { ds = getDataset(rs); if (ds == null) return; response.setHeader("XDODS-Server", getServerVersion()); response.setContentType("text/html"); response.setHeader("Content-Description", "dods-form"); // Utilize the getDDS() method to get a parsed and populated DDS // for this server. ServerDDS myDDS = ds.getDDS(); DAS das = ds.getDAS(); GetHTMLInterfaceHandler2 di = new GetHTMLInterfaceHandler2(); di.sendDataRequestForm(request, response, rs.getDataSet(), myDDS, das); } finally { // release lock if needed if (ds != null) ds.release(); } } /////////////////////////////////////////////////////////////////////////////////////////////// // debugging private void makeDebugActions() { DebugController.Category debugHandler = DebugController.find("ncdodsServer"); DebugController.Action act; act = new DebugController.Action("help", "Show help page") { public void doAction(DebugController.Event e) { try { doGetHELP(getRequestState(e.req, e.res)); } catch (Exception ioe) { log.error("ShowHelp", ioe); } } }; debugHandler.addAction(act); act = new DebugController.Action("version", "Show server version") { public void doAction(DebugController.Event e) { e.pw.println(" version= " + getServerVersion()); } }; debugHandler.addAction(act); } public String getServerName() { return this.getClass().getName(); } /*protected ReqState getRequestState(HttpServletRequest request, HttpServletResponse response) { ReqState rs = null; // The url and query strings will come to us in encoded form // (see HTTPmethod.newMethod()) String baseurl = request.getRequestURL().toString(); baseurl = EscapeStrings.escapeURL(baseurl); log.debug("doGet baseurl={}", baseurl); String query = request.getQueryString(); query = EscapeStrings.unescapeURLQuery(query); log.debug("doGet query={}", query); try { rs = new ReqState(request, response, getServletConfig(), getServerName(), baseurl, query); } catch (BadURLException bue) { rs = null; } return rs; }*/ /** * ************************************************************************ * Prints the OPeNDAP Server help page to the passed PrintWriter * * @param pw PrintWriter stream to which to dump the help page. */ private void printHelpPage(PrintWriter pw) { pw.println("<h3>OPeNDAP Server Help</h3>"); pw.println("To access most of the features of this OPeNDAP server, append"); pw.println("one of the following a eight suffixes to a URL: .das, .dds, .dods, .ddx, .blob, .info,"); pw.println(".ver or .help. Using these suffixes, you can ask this server for:"); pw.println("<dl>"); pw.println("<dt> das </dt> <dd> Dataset Attribute Structure (DAS)</dd>"); pw.println("<dt> dds </dt> <dd> Dataset Descriptor Structure (DDS)</dd>"); pw.println("<dt> dods </dt> <dd> DataDDS object (A constrained DDS populated with data)</dd>"); pw.println("<dt> ddx </dt> <dd> XML version of the DDS/DAS</dd>"); pw.println("<dt> blob </dt> <dd> Serialized binary data content for requested data set, " + "with the constraint expression applied.</dd>"); pw.println("<dt> info </dt> <dd> info object (attributes, types and other information)</dd>"); pw.println("<dt> html </dt> <dd> html form for this dataset</dd>"); pw.println("<dt> ver </dt> <dd> return the version number of the server</dd>"); pw.println("<dt> help </dt> <dd> help information (this text)</dd>"); pw.println("</dl>"); pw.println("For example, to request the DAS object from the FNOC1 dataset at URI/GSO (a"); pw.println("test dataset) you would appand `.das' to the URL:"); pw.println("http://opendap.gso.url.edu/cgi-bin/nph-nc/data/fnoc1.nc.das."); pw.println("<p><b>Note</b>: Many OPeNDAP clients supply these extensions for you so you don't"); pw.println("need to append them (for example when using interfaces supplied by us or"); pw.println("software re-linked with a OPeNDAP client-library). Generally, you only need to"); pw.println("add these if you are typing a URL directly into a WWW browser."); pw.println("<p><b>Note</b>: If you would like version information for this server but"); pw.println("don't know a specific data file or data set name, use `/version' for the"); pw.println("filename. For example: http://opendap.gso.url.edu/cgi-bin/nph-nc/version will"); pw.println("return the version number for the netCDF server used in the first example. "); pw.println("<p><b>Suggestion</b>: If you're typing this URL into a WWW browser and"); pw.println("would like information about the dataset, use the `.info' extension."); pw.println("<p>If you'd like to see a data values, use the `.html' extension and submit a"); pw.println("query using the customized form."); } //************************************************************************** /** * ************************************************************************ * Prints the Bad URL Page page to the passed PrintWriter * * @param pw PrintWriter stream to which to dump the bad URL page. */ private void printBadURLPage(PrintWriter pw) { String serverContactName = ThreddsConfig.get("serverInformation.contact.name", "UNKNOWN"); String serverContactEmail = ThreddsConfig.get("serverInformation.contact.email", "UNKNOWN"); pw.println("<h3>Error in URL</h3>"); pw.println("The URL extension did not match any that are known by this"); pw.println("server. Below is a list of the five extensions that are be recognized by"); pw.println("all OPeNDAP servers. If you think that the server is broken (that the URL you"); pw.println("submitted should have worked), then please contact the"); pw.println("administrator of this server [" + serverContactName + "] at: "); pw.println("<a href='mailto:" + serverContactEmail + "'>" + serverContactEmail + "</a><p>"); } /////////////////////////////////////////////////////// // utils protected ReqState getRequestState(HttpServletRequest request, HttpServletResponse response) { // Assume url was encoded String baseurl = request.getRequestURL().toString(); baseurl = EscapeStrings.unescapeURL(baseurl); // Assume query was encoded String query = request.getQueryString(); query = EscapeStrings.unescapeURLQuery(query); log.debug(String.format("OpendapServlet: nominal url: %s?%s", baseurl, query)); ReqState rs; try { rs = new ReqState(request, response, this, getServerName(), baseurl, query); } catch (Exception bue) { rs = null; } return rs; } private void checkSize(ServerDDS dds, boolean isAscii) throws Exception { long size = computeSize(dds, isAscii); //System.err.printf("total (constrained) size=%s\n", size); log.debug("total (constrained) size={}", size); double dsize = size / (1000.0 * 1000.0); double maxSize = isAscii ? ascLimit : binLimit; // Mbytes if (dsize > maxSize) { log.info("Reject request size = {} Mbytes", dsize); throw new UnsupportedOperationException("Request too big=" + dsize + " Mbytes, max=" + maxSize); } } private static final boolean debugSize = false; // Recursively compute size of the dds to be returned // Note that the dds may be empty (e-support ZTH-269982) private long computeSize(DConstructor ctor, boolean isAscii) throws Exception { long projectsize = 0; // accumulate size of projected variables long othersize = 0; // accumulate size of non-projected variables long fieldsize = 0; int projectedcount = 0; int fieldcount = 0; Enumeration vars = ctor.getVariables(); while (vars.hasMoreElements()) { fieldcount++; BaseType field = (BaseType) vars.nextElement(); fieldsize = computeFieldSize(field, isAscii); // accumulate the field sizes if (field.isProject()) { projectsize += fieldsize; projectedcount++; } else { othersize += fieldsize; } if (debugSize) { System.out.printf(" computeSize field %s isProject %s fieldsize=%d%n", field.getLongName(), field.isProject(), fieldsize); } } // Cases to consider: // 1. If all of the fields of this ctor are projected, // then return projectsize // 2. If none of the fields of this ctor are projected, // then return othersize // 3. otherwise, at least one field, but not all, is projected, // => return projectsize; long result; if (projectedcount == fieldcount) result = projectsize; else if (projectedcount == 0) result = othersize; else { assert (projectedcount > 0 && projectedcount < fieldcount); result = projectsize; } if (debugSize) { System.out.printf(" computeSize return=%d (%d, %d) %n", result, projectedcount, fieldcount); } return result; } long computeFieldSize(BaseType bt, boolean isAscii) throws Exception { long fieldsize = 0; // Figure out what this field is (e.g. primitive or not) // Somewhat convoluted. if (bt instanceof DConstructor) { // simple struct, seq, or grid => recurse fieldsize = computeSize((DConstructor) bt, isAscii); } else if (bt instanceof DArray) { SDArray da = (SDArray) bt; // Separate structure arrays from primitive arrays if (da.getContainerVar() instanceof DPrimitive) { fieldsize = computeArraySize(da); } else if (da.getContainerVar() instanceof DStructure) { fieldsize = computeSize((DStructure) da.getContainerVar(), isAscii); // recurse } else { // Some kind of problem throw new NoSuchTypeException("Computesize: unexpected type for " + bt.getLongName()); } } else if (bt instanceof DPrimitive) { DPrimitive dp = (DPrimitive) bt; if (dp instanceof DString) { String v = ((DString) dp).getValue(); fieldsize = (v == null ? 0 : v.length()); } else { DataType dtype = DODSNetcdfFile.convertToNCType(bt); fieldsize = dtype.getSize(); } } else { // Some kind of problem throw new NoSuchTypeException("Computesize: unknown type for " + bt.getLongName()); } return fieldsize; } long computeArraySize(SDArray da) throws Exception { assert (da.getContainerVar() instanceof DPrimitive); BaseType base = da.getPrimitiveVector().getTemplate(); DataType dtype = DODSNetcdfFile.convertToNCType(base); int elemSize = dtype.getSize(); int n = da.numDimensions(); List<Range> ranges = new ArrayList<>(n); long size = 0; for (int i = 0; i < n; i++) ranges.add(new Range(da.getStart(i), da.getStop(i), da.getStride(i))); Section s = new Section(ranges); size += s.computeSize() * elemSize; return size; } /* * *********************** dataset caching *********************************************** */ // any time the server needs access to the dataset, it gets a "GuardedDataset" which allows us to add caching // optionally, a session may be established, which allows us to reserve the dataset for that session. protected GuardedDataset getDataset(ReqState preq) throws Exception { HttpServletRequest req = preq.getRequest(); String reqPath = preq.getDataSet(); // see if the client wants sessions boolean acceptSession = false; String s = req.getHeader("X-Accept-Session"); if (s != null && s.equalsIgnoreCase("true") && allowSessions) acceptSession = true; HttpSession session = null; if (acceptSession) { // see if theres already a session established, create one if not session = req.getSession(); if (!session.isNew()) { GuardedDataset gdataset = (GuardedDataset) session.getAttribute(reqPath); if (null != gdataset) { if (debugSession) System.out.printf(" found gdataset %s in session %s %n", reqPath, session.getId()); if (log.isDebugEnabled()) log.debug(" found gdataset " + gdataset + " in session " + session.getId()); return gdataset; } } } /* test debug for (int i = 0; i < 2; i++) { NetcdfFile ncfile = DatasetHandler.getNetcdfFile(req, preq.getResponse(), reqPath); if (null == ncfile) return null; NetcdfDataset ncd = new NetcdfDataset(ncfile); Formatter out = new Formatter(); CompareNetcdf2.compareFiles(ncfile, ncd, out, false, false, false); System.out.printf("%s%n", out); FeatureDataset featureDataset = ucar.nc2.ft.FeatureDatasetFactoryManager.wrap(ucar.nc2.constants.FeatureType.STATION, ncd, null, new Formatter(System.err)); System.out.printf("%s%n", featureDataset); ncd.close(); } */ NetcdfFile ncd = DatasetHandler.getNetcdfFile(req, preq.getResponse(), reqPath); if (null == ncd) return null; // error message already sent // throw new FileNotFoundException("Cant find "+ reqPath); GuardedDataset gdataset = new GuardedDatasetCacheAndClone(reqPath, ncd, acceptSession); if (acceptSession) { String cookiePath = req.getRequestURI(); String suffix = "." + preq.getRequestSuffix(); if (cookiePath.endsWith(suffix)) // snip off the suffix cookiePath = cookiePath.substring(0, cookiePath.length() - suffix.length()); session.setAttribute(reqPath, gdataset); session.setAttribute(CookieFilter.SESSION_PATH, cookiePath); if (debugSession) System.out.printf(" added gdataset %s in session %s cookiePath %s %n", reqPath, session.getId(), cookiePath); if (log.isDebugEnabled()) log.debug(" added gdataset " + gdataset + " in session " + session.getId()); } return gdataset; } ////////////////////////////////////////////////////////////////////////////// public void parseExceptionHandler(ParseException pe, HttpServletResponse response) { try { BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream()); response.setHeader("Content-Description", "dods-error"); response.setContentType("text/plain"); String msg = pe.getMessage().replace('\"', '\''); DAP2Exception de2 = new DAP2Exception(opendap.dap.DAP2Exception.CANNOT_READ_FILE, msg); de2.print(eOut); } catch (Exception e) { System.err.println("parseExceptionHandler: " + e); } } public void dap2ExceptionHandler(DAP2Exception de, ReqState rs) { rs.getResponse().setHeader("Content-Description", "dods-error"); rs.getResponse().setContentType("text/plain"); try { de.print(rs.getResponse().getOutputStream()); } catch (Exception e) { System.err.println("dap2ExceptionHandler: " + e); } } private void sendErrorResponse(HttpServletResponse response, int errorCode, String errorMessage) { try { response.setStatus(errorCode); response.setHeader("Content-Description", "dods-error"); response.setContentType("text/plain"); PrintWriter pw = new PrintWriter(response.getOutputStream()); pw.println("Error {"); pw.println(" code = " + errorCode + ";"); pw.println(" message = \"" + errorMessage + "\";"); pw.println("};"); pw.flush(); } catch (Exception e) { System.err.println("sendErrorResponse: " + e); } } }