如何更好地设计解决主机/端口查找的方案

3
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个回答

3

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

至于具体的问题,您可以定义一个HTML <base>标签,使文档中的所有相对URL都变为相对于它。

<%@ 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函数中。更重要的是,一些MVC框架(如JSF)也有使用MessageFormat的标记。例如:

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

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>

不,上述假设它正在现有容器上的servlet中进行处理。我需要灵活性,以便将其指向不同的服务器以实现高可扩展性。 - user339108
@user339108 你可以在服务器上添加重写规则来跳转到正确的主机。 - Steve-o
为了实现高可扩展性,将服务器放在负载均衡器后面,这样您就不必关心哪个服务器正在被访问。如果您正在Session中存储信息,请停止这样做。 - Sixty4Bit

0

有几种方法可以实现这一点,这有点取决于需要从中提取URI的HTML的类型。如果您只涉及图像、链接、样式表和其他特定于HTML的导入,则可以尝试基于DNS重定向的方法。我假设您可以访问一些额外的资源,具体来说是Web服务器或Servlet容器,并且可以控制指向该服务器的DNS。

首先,在创建文档时解析文档以提取所有引用的主机名(或者如果只是特定的主机名,则查找它们)。然后使用这些主机名(如果有指定端口,则使用端口)作为数据库查找的关键字。将主机名转换为唯一代码。如果没有找到该主机名的条目,请创建一个并添加它。基本上将主机名转换为代表性代码。现在进行一些DNS诡计。将生成的唯一代码附加到已设置为指向前述Web服务器的某些DNS上。要做到这一点,您需要设置DNS通配符。因此,以一个例子来说明。假设您的源内容如下:

<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,它具有复杂的安全模型。您可能可以通过深入研究跨域策略的复杂世界来使其正常工作。


0

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

基本上,您在数据库中存储动态参数。例如,即使在开发模式和生产模式下,我也可以看到[somehost]发生变化。为了解决这个问题,我创建了一个类来批量替换变量,我称之为Replacer类(以下是代码)。

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

稍后创建一个Properties对象,静态地创建并填充一个静态的Replacer,并在从数据库中获取的所有数据上运行替换器。

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\".");

}

}


0

你可以尝试一下

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

请查看Java 文档
或者,您可以尝试

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

HTML已保存在数据库中。当在JSP中打印上述HTML时,您将如何评估那些(丑陋的)脚本表达式?这种方式肯定行不通。此外,如果返回“localhost”,InetAddress永远不会起作用。 - BalusC

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)中获取占位符版本,填充值,然后将其存储在b)的表/文件中。然后您的应用程序需要使用此表/文件中的最终字符串。您只需要在主机和端口更改时运行此脚本。


0

你需要重构你的优化,以不使用廉价的 hack 来达到你的目标。

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

相反,在应用程序服务器端创建一个适当的缓存。为了获得最大的页面组合性能,将你的公共文本元素减少成字节数组(如果你想要,可以 hack 一个 XML 序列化器),并使用每个线程的 writer 应用 'somehost'、'someport' 和 'id'。

如果不需要访问数据库,这将给你每秒数百次的命中率。如果你成功地构建和填充你的缓存(分而治之),通过哈希你的输入请求,如果缓存命中,你可能会在正常的服务器上获得超过 2000 次/秒的命中率。

通过使用像 Varnish 这样的缓存进一步优化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接