什么是更好的设计来解决主机/端口查找

时间:2011-03-18 10:42:06

标签: java html web-applications servlets

static image (e.g. car.png)
<table><tr><td><img src="http://<somehost>:<someport>/images/car.png" /></td></tr></table>

(e.g. lookup by id=123456 and fetched via a servlet from the database)
<table><tr><td><img src="http://<somehost>:<someport>/doc?id=123456"/></td></tr></table>

我们生成HTML代码片段(如上所述)并将其存储在数据库中,该数据库用于以动态方式重新构建用户特定页面。

上述场景中的问题是somehost / someport被静态绑定并存储在数据库中,我想避免,因为如果我必须升级到具有不同IP的不同机器,则所有上述调用都将失败。

如何以通用的方式解决这个问题,以便我可以在稍后的阶段绑定主机/端口。

7 个答案:

答案 0 :(得分:3)

首先,将HTML存储在数据库中并不是一个好主意。但是ala。

至于具体问题,你可以定义一个HTML <base>标签,它会使文件中的所有相对网址变为相对的。

<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
...
<head>
    <c:set var="r" value="${pageContext.request}" />
    <base href="${fn:replace(r.requestURL, r.requestURI, '')}${r.contextPath}/" />
</head>

这样你就可以使用

<table><tr><td><img src="images/car.png" /></td></tr></table>
<table><tr><td><img src="doc?id=123456"/></td></tr></table>

没有基础,你将依赖于上下文路径。


如果确实想要对它们进行参数化,那么我会使用java.text.MessageFormat。您可以使用{0}{1}{2}等作为第一,第二,第三等参数的占位符。

<table><tr><td><img src="{0}/images/car.png" /></td></tr></table>
<table><tr><td><img src="{0}/doc?id=123456"/></td></tr></table>

您可以从HttpServletRequest获取当前主机/端口(和上下文!),如下所示:

HttpServletRequest r = getItSomehow();
String base = r.getRequestURL().toString().replace(r.getRequestURI(), "") + r.getContextPath();

您可以按如下方式格式化数据库中的HTML:

String html = getItSomehow();
String formatted = MessageFormat.format(html, base);

然后在JSP中显示它。您甚至可以将其包装在自定义EL功能中。更重要的是,像JSF这样的一些MVC框架也有使用MessageFormat的标签。 E.g。

<h:outputFormat value="#{bean.html}" escape="false">
    <f:param value="#{bean.base}" />
</h:outputFormat>

答案 1 :(得分:1)

你为什么不用这种方式?这应该工作

static image (e.g. car.png)
<table><tr><td><img src="images/car.png" /></td></tr></table>

(e.g. lookup by id=123456 and fetched via a servlet from the database)
<table><tr><td><img src="/doc?id=123456"/></td></tr></table>

答案 2 :(得分:0)

你可以尝试

<img src="http://<%=request.getServerName()%>:request.getServerPort()/images/car.png" />

查看Java docs
或者,您可以尝试

<% @page import="java.net.InetAddress" %>
<%
InetAddress ia = InetAddress.getLocalHost();
String hostName = ia.getHostName();
%>

答案 3 :(得分:0)

基本问题是您在数据库中有特定文本,并且您需要能够在某个时间点替换它的某些部分。 除了占位符之外,别无选择。

你现在可以做两件事:

a)将html存储在带有占位符的数据库中:

<table><tr><td><img src="http://%1:%2/images/car.png" /></td></tr></table>

并使用

替换占位符
String.format(strHtmlFromDatabase, strHost, strPort);

缺点是每次都会替换字符串(在每次请求时)。所以你应该把它缓存在内存中,或者:

b)执行与a)相同的操作,并使用第二个数据库表甚至文件来存储html字符串的“最终”(完全替换)版本。然后编写一个小型生成器脚本,从a)获取spaceholder-version,填入值,然后将其存储在b)的表/文件中。然后,您的应用需要使用此表/文件中的最终字符串。每次主机和端口更改时,您只需要运行此脚本。

答案 4 :(得分:0)

您需要重构优化,以便在没有廉价黑客的情况下获得目标。

您应该将HTML生成器输入存储在数据库而不是输出中,从数据库传输文本比在应用程序服务器端生成文本几乎没有效率。实际上,所有数据库访问都很糟糕,应该避免使用。

而是在应用程序服务器端创建适当的缓存。为了在页面组合方面获得最佳性能,请将常用文本元素减少为字节数组(如果需要,可以破解XML序列化程序),并使用每线程编写器应用“somehost”,“someport”和“id”。

如果不需要数据库访问,这将为您提供每秒几百次点击。如果您设法好好构建和填充缓存(分而治之),通过对输入请求进行哈希处理,如果在正常服务器上命中缓存,则每秒可能会获得超过2000次点击。

使用Varnish等缓存进一步优化。

答案 5 :(得分:0)

有几种方法可以实现这一点,这取决于需要从哪种HTML中提取这些URI。如果您只处理图像,链接,样式表和其他HTML特定的导入,那么您可以尝试基于DNS重定向的方法。我将假设您可以访问一些额外的资源,特别是Web服务器或servlet容器,并且能够控制到该服务器的DNS。

首先,在创建时解析文档以提取所有引用的主机名(或者如果它只是特定的主机名,请查找它们)。然后使用这些主机名(以及指定的端口)作为数据库查找的键。将主机名转换为唯一代码。如果找不到该主机名的条目,请创建一个并添加。基本上将您的主机名转换为代表性代码。现在为一些DNS欺骗。获取您的unqiuely生成的代码并将其附加到已设置为指向上述Web服务器的某个DNS。为此,您需要设置DNS wildcards。所以举个例子。假设您的源内容如下:

<p>Why is it that all my baking ends up on <a href="http://cakewrecks.blogspot.com/p/faq.html">the internet</a></p>

您提取主机名cakewrecks.blogspot.com。在转换表中查找并将其转换为代表性代码,例如11ag3。然后将其附加到Web服务器主机名,例如11ag3.content.mycompany.com

现在,您将获取新的主机名并将其重新插入到您的内容中。

<p>Why is it that all my baking ends up on <a href="http://11ag3.content.mycompany.com/p/faq.html">the internet</a></p>

接下来,您需要编写一个servlet(或者可能还有一些其他动态代码)。这应该是拦截任何传入的HTTP请求(您的内容中的所有请求现在应该最终在此servlet而不是它们最初的位置)。因此servlet收到

的请求
http://11ag3.content.mycompany.com/p/faq.html

它提取主机名11ag3.content.mycompany.com,并从中提取唯一代码11ag3。它现在与我们在创建文档时所做的相反,并查找原始主机名,并将其放回请求中,从而重构http://cakewrecks.blogspot.com/p/faq.html。它现在使用HTTP 300状态代码(可能是307,临时重定向)以及重建的URL来响应请求。

这里的一大优势是您现在拥有一个包含所有主机名的数据库表。如果要更改某些内容的提供位置,只需使用新主机名更新相应的条目即可。此外,您可以避免每次服务时模板化每个页面的开销。

这种方法的主要问题可能是flash,它具有复杂的安全模型。您可以通过深入研究cross domain policies的复杂世界来实现它。

答案 6 :(得分:0)

这个解决方案实际上是其他人所评论的,但这是一种更优雅的替换方式,以及我在应用程序中使用的方法。

您的数据库中存储的参数几乎都是动态的。例如,即使您处于开发模式而非生产模式,我也可以看到[somehost]发生变化。我为解决这个问题而创建的是一个大规模替换变量的类,我称之为Replacer类(下面的代码)。

我建议您将[somehost]和所有[...]参数存储在数据库中作为参数,例如:[x],[y],[z]。如果你可以将x,yz放在枚举中并将它们的序号保存在数据库中,例如数据库中的[1],[2],[3],那么你将在数据库驱动器上节省大量空间。 / p>

稍后创建一个静态创建填充静态替换器的Properties对象,并对数据库中出现的所有数据运行替换器。

public class Replacer {

private final Map<String, Object> replacements = new HashMap<String, Object>();

public Replacer () {

    replacements.put("''", "\"");

}

public void addReplacement (String replaceWhat, Object replaceWith) {

    replacements.put(replaceWhat, replaceWith);

}

public String replace ( Object contentToReplace ) {

    String output = contentToReplace.toString();

    for (String replacement : replacements.keySet() ) {

        output = output.replace(replacement, replacements.get(replacement).toString() );

    }

    return output;

}

public static void main (String[] args) throws Exception {

    testReplaceTwoSingleQuote();

}

public static void testReplaceTwoSingleQuote () throws Exception {

    Replacer rep = new Replacer();

    assert rep.replace( "And Mary said, ''Hello Bob''. ").equals("And Mary said, \"Hello Bob\".");

}

}