在 Java Web 应用程序中从应用程序服务器外部提供静态数据的最简单方法
Posted
技术标签:
【中文标题】在 Java Web 应用程序中从应用程序服务器外部提供静态数据的最简单方法【英文标题】:Simplest way to serve static data from outside the application server in a Java web application 【发布时间】:2010-12-21 04:47:32 【问题描述】:我有一个在 Tomcat 上运行的 Java Web 应用程序。我想加载将在 Web UI 和应用程序生成的 PDF 文件中显示的静态图像。还将通过 Web UI 上传来添加和保存新图像。
通过将静态数据存储在 Web 容器中来做到这一点不是问题,但从 Web 容器外部存储和加载它们让我很头疼。
此时我不希望使用像 Apache 这样的单独 Web 服务器来提供静态数据。我也不喜欢将图像以二进制形式存储在数据库中的想法。
我已经看到了一些建议,例如将图像目录作为指向 Web 容器外部目录的符号链接,但是这种方法是否适用于 Windows 和 *nix 环境?
有些人建议编写一个过滤器或一个 servlet 来处理图像服务,但这些建议非常模糊和高级,没有提供有关如何完成此操作的更详细信息的指针。
【问题讨论】:
【参考方案1】:您可以通过将图像放在固定路径上(例如:/var/images 或 c:\images),在应用程序设置中添加设置(在我的示例中由 Settings.class 表示),然后像这样在你的HttpServlet
中加载它们:
String filename = Settings.getValue("images.path") + request.getParameter("imageName")
FileInputStream fis = new FileInputStream(filename);
int b = 0;
while ((b = fis.read()) != -1)
response.getOutputStream().write(b);
或者如果你想操作图像:
String filename = Settings.getValue("images.path") + request.getParameter("imageName")
File imageFile = new File(filename);
BufferedImage image = ImageIO.read(imageFile);
ImageIO.write(image, "image/png", response.getOutputStream());
那么html代码就是<img src="imageServlet?imageName=myimage.png" />
当然,您应该考虑提供不同的内容类型 - “image/jpeg”,例如基于文件扩展名。您还应该提供一些缓存。
此外,您可以使用此 servlet 对图像进行质量重新缩放,方法是提供宽度和高度参数作为参数,并使用 image.getScaledInstance(w, h, Image.SCALE_SMOOTH
),当然要考虑性能。
【讨论】:
你真的不需要Java 2D API,它只会增加不必要的开销。只需读取 InputStream 并写入 OutputStream。 是的,我以重新缩放和其他操作的想法开始响应,但最终简化了它。【参考方案2】:我已经看到了一些建议,例如将图像目录作为指向 Web 容器外部目录的符号链接,但是这种方法是否适用于 Windows 和 *nix 环境?
如果您遵守 *nix 文件系统路径规则(即,您只使用 /path/to/files
中的正斜杠),那么它也可以在 Windows 上工作,而无需摆弄丑陋的 File.separator
字符串连接。但是,它只会在与调用此命令的位置相同的工作磁盘上进行扫描。因此,如果 Tomcat 安装在 C:
上,那么 /path/to/files
实际上将指向 C:\path\to\files
。
如果文件都位于webapp之外,并且你想让Tomcat的DefaultServlet
来处理它们,那么在Tomcat中你基本上需要做的就是在<Host>
里面的/conf/server.xml
中添加以下Context元素标签:
<Context docBase="/path/to/files" path="/files" />
这样他们就可以通过http://example.com/files/...
访问。对于基于 Tomcat 的服务器,例如 JBoss EAP 6.x 或更早版本,方法基本相同,另见 here。 GlassFish/Payara 配置示例可以找到here,WildFly 配置示例可以找到here。
如果您想自己控制读/写文件,那么您需要为此创建一个Servlet
,它基本上只是获取文件的InputStream
,例如FileInputStream
,并将其写入HttpServletResponse
的 OutputStream
。
在响应中,您应该设置Content-Type
标头,以便客户端知道哪个应用程序与提供的文件相关联。并且,您应该设置Content-Length
标头,以便客户端可以计算下载进度,否则将是未知的。而且,如果您想要一个 另存为 对话框,您应该将 Content-Disposition
标头设置为 attachment
,否则客户端将尝试内联显示它。最后只需将文件内容写入响应输出流即可。
以下是此类 servlet 的基本示例:
@WebServlet("/files/*")
public class FileServlet extends HttpServlet
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
String filename = URLDecoder.decode(request.getPathInfo().substring(1), "UTF-8");
File file = new File("/path/to/files", filename);
response.setHeader("Content-Type", getServletContext().getMimeType(filename));
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Disposition", "inline; filename=\"" + file.getName() + "\"");
Files.copy(file.toPath(), response.getOutputStream());
当映射到例如/files/*
的url-pattern
时,您可以通过http://example.com/files/image.png
调用它。通过这种方式,您可以比DefaultServlet
更好地控制请求,例如提供默认图像(即if (!file.exists()) file = new File("/path/to/files", "404.gif")
左右)。同样使用request.getPathInfo()
比request.getParameter()
更受欢迎,因为它对SEO 更友好,否则IE 在另存为 期间不会选择正确的文件名。
您可以重复使用相同的逻辑来从数据库中提供文件。只需将new FileInputStream()
替换为ResultSet#getInputStream()
。
希望这会有所帮助。
另见:
Recommended way to save uploaded files in a servlet application Abstract template for a static resource servlet(支持HTTP缓存) How to retrieve and display images from a database in a JSP page? How to stream audio/video files such as MP3, MP4, AVI, etc using a Servlet【讨论】:
@SalutonMondo:最省力的方法。 @BalusC,我在 Windows 中试过这个:<Context docBase="/path/to/images" path="/images" />
,但获取相对于 webapps 文件夹的路径:C:\install\apache-tomcat-8.0.26\webapps\tmp] is not valid
在 Windows 上应该是:<Context docBase="C:\tmp\" path="/images" />
这个答案有几个问题,尽管它非常简洁地回答了原始问题。我发布此内容是希望在答案中添加说明,以便读者了解这不是“解决方案”。首先,这个 servlet 向任何部署它的应用程序添加了一个巨大的路径遍历错误。这是要提到的最重要的事情。
@BalusC 是的,我很欣赏可以从这个答案中找到更安全、功能更齐全的解决方案。我只是认为值得在您的示例中添加代码注释,例如“TODO:缓解目录遍历”或类似的内容。不幸的是,SO 是当今软件中复制/粘贴解决方案的重要来源。【参考方案3】:
如果您决定发送到FileServlet
,那么您还需要context.xml
中的allowLinking="true"
以允许FileServlet
遍历符号链接。
见http://tomcat.apache.org/tomcat-6.0-doc/config/context.html
【讨论】:
【参考方案4】:要求:从 WEBROOT 目录外部或本地磁盘访问静态资源(图像/视频等)
第 1 步: 在tomcat服务器的webapps下创建一个文件夹,假设文件夹名是myproj
第2步:在myproj下创建一个WEB-INF文件夹,在这个文件夹下创建一个简单的web.xml
web.xml下的代码
<web-app>
</web-app>
以上两步的目录结构
c:\programfile\apachesoftwarefoundation\tomcat\...\webapps
|
|---myproj
| |
| |---WEB-INF
| |
|---web.xml
第 3 步: 现在在以下位置创建一个名为 myproj.xml 的 xml 文件
c:\programfile\apachesoftwarefoundation\tomcat\conf\catalina\localhost
myproj.xml 中的代码:
<Context path="/myproj/images" docBase="e:/myproj/" crossContext="false" debug="0" reloadable="true" privileged="true" />
第 4 步: 4 A) 现在在硬盘的 E 盘中创建一个名为 myproj 的文件夹并新建一个
带有名称 images 的文件夹并将一些图像放入 images 文件夹 (e:myproj\images\)
假设 myfoto.jpg 放在e:\myproj\images\myfoto.jpg
下
4 B) 现在在e:\myproj\WEB-INF
中创建一个名为 WEB-INF 的文件夹,并在 WEB-INF 文件夹中创建一个 web.xml
web.xml 中的代码
<web-app>
</web-app>
第 5 步:现在创建一个名为 index.html 的 .html 文档并放在 e:\myproj 下
index.html 下的代码 欢迎来到 Myproj
上述第4步和第5步的目录结构如下
E:\myproj
|--index.html
|
|--images
| |----myfoto.jpg
|
|--WEB-INF
| |--web.xml
第 6 步:现在启动 apache tomcat 服务器
第七步:打开浏览器,输入如下网址
http://localhost:8080/myproj
然后显示 index.html 中提供的内容
第 8 步:访问本地硬盘下的图像(在 webroot 之外)
http://localhost:8080/myproj/images/myfoto.jpg
【讨论】:
您能否建议我,如何为动态值做同样的事情。我的意思是我想将数据(xml)写入我的本地目录,或者在我的 jsp 页面中读取它。有什么方法可以写入服务器管理的目录,以便我使用上述过程访问它们?? 虽然我可以正常运行 index.html 文件但网络浏览器中没有显示图像 我的错误帖子运行良好我只是忘记将 / 放在 E:/myproj 的末尾我将其更改为 E:/myproj/ 并且它运行良好谢谢@sbabamca 您好,感谢您的帖子,它非常有用。在这里,我希望通过接口将文件上传到该特定目录。我希望为此启用 POST 方法。任何人都可以帮助我吗?【参考方案5】:添加到 server.xml:
<Context docBase="c:/dirtoshare" path="/dir" />
在 web.xml 中启用 dir 文件列表参数:
<init-param>
<param-name>listings</param-name>
<param-value>true</param-value>
</init-param>
【讨论】:
随着 web.xml 的变化,我可以获得一个文件列表,以便将其发送到选择框? 此更改将在 tomcat 的 web.xml 中,而不是您的应用程序中 谢谢!是否需要在 web.xml 中启用 dir 文件列表参数?【参考方案6】:这是我工作场所的故事: - 我们尝试使用 Struts 1 和 Tomcat 7.x 上传多个图像和文档文件。 - 我们尝试将上传的文件写入文件系统、文件名和数据库记录的完整路径。 - 我们尝试在 Web 应用程序目录 之外分离文件夹。 (*)
以下解决方案非常简单,对需求(*)有效:
在文件 META-INF/context.xml
文件中包含以下内容:
(例如,我的应用程序在http://localhost:8080/ABC
运行,我的应用程序/项目名为ABC
)。
(这也是文件context.xml
的完整内容)
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/ABC" aliases="/images=D:\images,/docs=D:\docs"/>
(适用于 Tomcat 7 或更高版本)
结果:我们已经创建了 2 个别名。例如,我们将图像保存在:D:\images\foo.jpg
并从链接或使用图像标签查看:
<img src="http://localhost:8080/ABC/images/foo.jsp" >
或
<img src="/images/foo.jsp" >
(我使用 Netbeans 7.x,Netbeans 似乎自动创建文件WEB-INF\context.xml
)
【讨论】:
【参考方案7】:我做得更简单。问题:CSS 文件有指向 img 文件夹的 url 链接。获取 404。
我查看了不存在的网址http://tomcatfolder:port/img/blablah.png。但是,这实际上是指向 Tomcat 中的 ROOT 应用程序。
所以我只是将我的 webapp 中的 img 文件夹复制到了那个 ROOT 应用程序中。作品!
当然不推荐用于生产,但这是用于内部工具开发应用程序。
【讨论】:
【参考方案8】:如果有人无法通过接受的答案解决他的问题,请注意以下注意事项:
-
无需提及
localhost:<port>
和<img> src
属性。
确保您在 Eclipse 之外运行此项目,因为 Eclipse 会在其本地 server.xml
文件中自行创建 context docBase
条目。
【讨论】:
【参考方案9】:读取文件的InputStream并写入ServletOutputStream
,用于向客户端发送二进制数据。
@WebServlet("/files/URLStream")
public class URLStream extends HttpServlet
private static final long serialVersionUID = 1L;
public URLStream()
super();
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
File source = new File("D:\\SVN_Commit.PNG");
long start = System.nanoTime();
InputStream image = new FileInputStream(source);
/*String fileID = request.getParameter("id");
System.out.println("Requested File ID : "+fileID);
// Mongo DB GridFS - https://***.com/a/33544285/5081877
image = outputImageFile.getInputStream();*/
if( image != null )
BufferedInputStream bin = null;
BufferedOutputStream bout = null;
ServletOutputStream sos = response.getOutputStream();
try
bin = new BufferedInputStream( image );
bout = new BufferedOutputStream( sos );
int ch =0; ;
while((ch=bin.read())!=-1)
bout.write(ch);
finally
bin.close();
image.close();
bout.close();
sos.close();
else
PrintWriter writer = response.getWriter();
writer.append("Something went wrong with your request.");
System.out.println("Image not available.");
System.out.println("Time taken by Stream Copy = "+(System.nanoTime()-start));
将 URL 直接指向 src
属性。
<img src='http://172.0.0.1:8080/ServletApp/files/URLStream?id=5a575be200c117cc2500003b' />
<img src='http://172.0.0.1:8080/ServletApp/files/URLStream' />
<video controls="controls" src="http://172.0.0.1:8080/ServletApp/files/URLStream"></video>
【讨论】:
【参考方案10】:如果您想使用 JAX-RS(例如 RESTEasy),请尝试以下操作:
@Path("/pic")
public Response get(@QueryParam("url") final String url)
String picUrl = URLDecoder.decode(url, "UTF-8");
return Response.ok(sendPicAsStream(picUrl))
.header(HttpHeaders.CONTENT_TYPE, "image/jpg")
.build();
private StreamingOutput sendPicAsStream(String picUrl)
return output ->
try (InputStream is = (new URL(picUrl)).openStream())
ByteStreams.copy(is, output);
;
使用javax.ws.rs.core.Response
和com.google.common.io.ByteStreams
【讨论】:
以上是关于在 Java Web 应用程序中从应用程序服务器外部提供静态数据的最简单方法的主要内容,如果未能解决你的问题,请参考以下文章
在基于 Java 的 Web 应用程序中从数据库到前端的实时数据复制