`
yaerfeng1989
  • 浏览: 234252 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

使用ssh开发rest web服务支持http etag header的教程详解

阅读更多

原创整理不易,转载请注明出处:使用ssh开发rest web服务支持http etag header的教程详解

代码下载地址:http://www.zuidaima.com/share/1777391667989504.htm


导言

REST方式的应用程序构架在近日所产生的巨大影响突出了Web应用程序的优雅设计的重要性。现在人们开始理解“WWW架构”内在的可测量性及弹性,并且已经开始探索使用其范例的更好的方式。在本文中,我们将讨论一个Web应用开发工具——“简陋的、卑下的”ETags,以及如何在基于SpringFramework的动态Web应用程序中集成这个工具,来提高应用的性能及可测性。

我们将要使用的基于Spring的应用程序是基于“petclinic”(宠物门诊?)的一个应用。在您下载的程序包中,包含了如何加入必要的配置和源代码让你亲自体验该程序的介绍。

什么是ETag
 
在HTTP协议规范中,ETag被定义为“被请求的变量的实体值”。(
参见 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html - Section 14.19。)换句话说,ETag是一个与Web资源相关联的标记。典型的Web资源是一个Web页面,但也可以是一个JSON格式或者XML格式的文档。服务器可以指出一个标记是什么及其意义,并将这个标记放在HTTP头重传送给客户端。

ETag如何提高应用程序性能

ETag和一个GET请求的“If-None-Match”头信息一起使用,服务器开发者以此来使用客户端缓存的优势。服务器在客户端的一次请求时产生ETag,并在以后的请求中判断被请求资源是否发生了变化。确切的说,客户端将这个标记传回给服务器,来验证它自己的缓存是否有效。

整个处理过程如下:

客户端请求页面A
服务器响应,返回页面A,附加ETag
客户端显示A,并将页面和ETag一并缓存
客户端再次请求页面A,请求中包含了上次请求页面A时返回的ETag
服务器检查客户端发送过来的ETag,并确定页面A在该客户端上次请求后到现在没有发生过变化,因此,发送一个304(未改变)响应头给客户端,附带一个空的响应体。

文章的剩余部分将讨论在基于SpringFramework的使用SpringMVC的Web应用程序中使用ETag两种方式。首先,我们将通过一个Servlet2.3 过滤器,使用由计算请求返回结果的MD5值而产生的ETag(一个简单的ETag实现)。第二种方式使用一种更加“专业”的方式通过跟踪页面呈现所用到的模型的变化来确定ETag的有效性(一个“专业”的ETag实现)。虽然我们在这里使用了Spring MVC,但这个技术适用于其他任何的MVC框架。

在继续之前,我们有必要明确,ETag技术是为了希望改进动态产生的页面的访问速度而提出的。作为一个完整的性能优化方案和性能分析,其他的性能优化技术依然应当被考虑。
 

自顶向下的Web缓存 本文首先讨论将HTTP缓存技术应用于动态页面。寻求Web应用程序优化方案时,我们应当采用一个完整的,自顶向下的步骤。从根本上说,理解HTTP请求的过程是很重要的,采用哪种具体的技术取决于你在什么场合。例如: Apache可以放在你的Servlet容易之前,来接受如图片,js请求,同时也可以使用FileETag指令产生ETag响应头。 使用Javascript优化技术,例如将多个js文件合并,并去除空格等无用信息。 利用GZip和Cache-Control响应头。 使用JamonPerformanceMonitorInterceptor确定你的Spring应用系统中的性能瓶颈。 确定你充分地使用了ORM工具的缓存机制,从而使得实体信息不是频繁的从数据库中重新加载。搞清楚如何让查询缓存很好的工作需要一定的时间。 确保尽量少聪数据库中重新加载数据,特别是一些大的列表。大列表应当被按页分割,对每一页的请求返回大列表的一个小的子集。 Session中保存尽量少的信息。这降低了内存要求,在建立应用层集群时将会显得非常有用。 使用一个数据库调试工具,确定查询时使用了哪些索引,查询时数据表将不会被锁定。 当然了,性能优化的最佳格言是适用的:测量两次,切割一次。(多次测试后再修改) 等等,上面的话是对木匠说的,但虽然如此,它一样适用于我们!

 

 一个内容主体ETag过滤器

我们将看到的第一种方式是建立一个Servlet过滤器基于页面内容(MVC中的View)来产生ETag标记。乍一看,使用这种方式对性能的提升似乎没什么大的作用。服务器依然需要声称页面,并且增加了计算标记值的时间。但是,在这里我们的目的是减少带宽占用。这对于很多的反应时间很长的情形是一个很大的益处,例如如果你的应用的服务器和客户端分别在地球的不同半球上。我曾看到一个从东京发出的对纽约的某台服务器的请求,响应长达350毫秒。考虑并发用户因素后,这将成为一个重大的瓶颈。


代码

我们用于产生标记的技术是计算页面返回内容的MD5值。创建一个响应包装器将完成这个工作。包装器使用一个字节数组来保存返回内容,在过滤器链处理完成之后,我们计算这个字节数组的MD5哈希值。

doFilter方法的实现如下:

Listing 1: ETagContentFilter.doFilter

public   void  doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  throws  IOException,
	ServletException  {
	HttpServletRequest servletRequest  =  (HttpServletRequest) req;
	HttpServletResponse servletResponse  =  (HttpServletResponse) res;

	ByteArrayOutputStream baos  =   new  ByteArrayOutputStream();
	ETagResponseWrapper wrappedResponse  =   new  ETagResponseWrapper(servletResponse, baos);
	chain.doFilter(servletRequest, wrappedResponse);

	byte [] bytes  =  baos.toByteArray();

	String token  =   ' " '   +  ETagComputeUtils.getMd5Digest(bytes)  +   ' " ' ;
	servletResponse.setHeader( " ETag " , token);  //  always store the ETag in the header

	String previousToken  =  servletRequest.getHeader( " If-None-Match " );
	if  (previousToken  !=   null   &&  previousToken.equals(token))  {  //  compare previous token with current one
	logger.debug( " ETag match: returning 304 Not Modified " );
	servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);
	//  use the same date we sent when we created the ETag the first time through
	servletResponse.setHeader( " Last-Modified " , servletRequest.getHeader( " If-Modified-Since " ));
	}   else    {    //  first time through - set last modified time to now 
	Calendar cal  =  Calendar.getInstance();
	cal.set(Calendar.MILLISECOND,  0 );
	Date lastModified  =  cal.getTime();
	servletResponse.setDateHeader( " Last-Modified " , lastModified.getTime());

	logger.debug( " Writing body content " );
	servletResponse.setContentLength(bytes.length);
	ServletOutputStream sos  =  servletResponse.getOutputStream();
	sos.write(bytes);
	sos.flush();
	sos.close();
	}
} 


应该注意到,我们设置了“Last-Modified”响应头。这是因为我们需要组织良好的内容格式,以对应哪些无法理解ETag响应头的客户端。
上面的示例代码用到了一个EtagComputeUtils工具类来产生一个对象的字节数组表示并处理MD5杂凑逻辑。在这里我使用javax.security.MessageDigest来计算MD5值。

Listing 2: ETagComputeUtils

public static byte[] serialize(Object obj) throws IOException {
	byte[] byteArray = null;
	ByteArrayOutputStream baos = null;
	ObjectOutputStream out = null;
	try {
		// These objects are closed in the finally.
		baos = new ByteArrayOutputStream();
		out = new ObjectOutputStream(baos);
		out.writeObject(obj);
		byteArray = baos.toByteArray();
	} finally {
		if (out != null) {
			out.close();
		}
	}
	return byteArray;
}

public static String getMd5Digest(byte[] bytes) {
	MessageDigest md;
	try {
		md = MessageDigest.getInstance("MD5");
	} catch (NoSuchAlgorithmException e) {
		throw new RuntimeException(
				"MD5 cryptographic algorithm is not available.", e);
	}
	byte[] messageDigest = md.digest(bytes);
	BigInteger number = new BigInteger(1, messageDigest);
	// prepend a zero to get a "proper" MD5 hash value
	StringBuffer sb = new StringBuffer('0');
	sb.append(number.toString(16));
	return sb.toString();
}


在Web.xml中调用这个过滤器是很简单的:

Listing 3: Configuration of the filter in web.xml.

 <filter> 
 <filter-name> ETag Content Filter </filter-name> 
 <filter-class> org.springframework.samples.petclinic.web.ETagContentFilter </filter-class> 
 </filter> 
 
 <filter-mapping> 
 <filter-name> ETag Content Filter </filter-name> 
 <url-pattern> /*.htm </url-pattern> 
 </filter-mapping>


每一个htm文件将被EtagContentFilter过滤,如果该文件在上次请求后没有发生变化,则返回一个空的HTTP响应体。

上面讨论的方式对于确定类型的页面很有用,但也有一些缺点。
页面在服务器段生成之后,在返回给客户端之前,我们计算了ETag值,如果ETag匹配,那么我们实在是没有必要去取出模型数据,因为渲染出来的页面将不会返回给客户端。
对于在页脚呈现日期和时间的页面,每次请求都是不同的,即使页面的主题内容并没有发生改变。
下面,我们将看到另一种可选的方法——通过理解构建页面的底层数据来解决上面的限制带来的问题。

ETag拦截器
Spring MVC中的HTTP请求传递途径包含了一种可以在控制器处理请求之前插入一个拦截器的能力。这对于插入ETag对比逻辑来说是一个极其合适的切入点,在这里,如果发现构建页面的数据没有发生变化,我们就可以停止更进一步的处理。
这里的诀窍是如何知道构建所请求的页面的数据没有发生变化。为了本文的目的,我创建了一个简单的ModifiedObjectTracker,通过Hiberante事件监听器来跟踪新增、更新、删除操作。跟踪器将为每一个页面保持一个为一个数字,以及一个影响到该页面的持久化实体的Map。如果一个POJO发生了变化,那么一个技术其将增加所有用到了这个POJO的页面对应的数字。将这个数字作为ETag,当客户端将ETag返回时,我们将会知道一个页面所用到的模型是否发生了变化。

代码


从ModifiedObjectTracker开始:

 public interface ModifiedObjectTracker {
     void notifyModified(> String entity);
 } 


很简单吧?它的实现会比较有意思。每当一个实体发生了变化,我们为每一个用到了该实体的页面更新对应的计数器。

public void notifyModified(String entity) {
	// entityViewMap is a map of entity -> list of view names
	List views = getEntityViewMap().get(entity);

	if (views == null) {
		return; // no views are configured for this entity
	}

	synchronized (counts) {
		for (String view : views) {
			Integer count = counts.get(view);
			counts.put(view, ++count);
		}
	}
}


一次“变化”就是一次新增、修改或者删除操作。下面是针对删除操作的处理器列表(作为事件监听器配置在Hibernate 3 LocalSessionFactoryBean中)。

public class DeleteHandler extends DefaultDeleteEventListener {
	private ModifiedObjectTracker tracker;

	public void onDelete(DeleteEvent event) throws HibernateException {
		getModifiedObjectTracker().notifyModified(event.getEntityName());
	}

	public ModifiedObjectTracker getModifiedObjectTracker() {
		return tracker;
	}

	public void setModifiedObjectTracker(ModifiedObjectTracker tracker) {
		this.tracker = tracker;
	}
}


ModifiedObjectTracker将通过Spring配置注射到DeleteHandler中。同时,将会有一个SaveOrUpdateHandler处理实体的新增和修改。
如果客户端发回了一个当前有效的ETag(意思是内容在上次请求后未曾发生改变),我们将阻止更多的处理逻辑,以实现我们的性能提升。在Spring MVC中,可以使用一个HandlerInterceptorAdaptor ,并重写preHandle方法:

public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws 
	 ServletException, IOException {
	 String method= request.getMethod();
	 if ( ! " GET " .equals(method))
	 return true ;

	 String previousToken= request.getHeader( " If-None-Match " );
	 String token= getTokenFactory().getToken(request);

	 // compare previous token with current one 
	 if ((token != null ) && (previousToken != null && previousToken.equals( ' " ' + token + ' " ' ))) {
	 response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
	 // re-use original last modified timestamp 
	 response.setHeader( " Last-Modified " , request.getHeader( " If-Modified-Since " ))
	 return false ; // no further processing required 
	 }

	 // set header for the next time the client calls 
	 if (token != null ) { 
	 response.setHeader( " ETag " , ' " ' + token + ' " ' );

	 // first time through - set last modified time to now 
	 Calendar cal= Calendar.getInstance();
	 cal.set(Calendar.MILLISECOND, );
	 Date lastModified= cal.getTime();
	 response.setDateHeader( " Last-Modified " , lastModified.getTime());
	 }

	 return true ;
}


首先我们需要确定我们处理的是一个GET请求(ETag可以在客户端发出PUT请求时验证更新是否冲突,但那已经超出了本文的范围)。如果标记和服务器上次返回的标记相匹配,则返回一个304位发生改变响应,并绕过后面的处理链。否则,我们设置一个ETag响应头,以备客户端下次请求同样的页面。

可以看到,我将产生标记的逻辑抽象出来形成了一个接口,如此我们则可以使用不同的标记生成策略。该接口只有一个方法:

public interface ETagTokenFactory {
 String getToken(HttpServletRequest request);
} 


为了少列出一些代码,我的SampleTokenFactory实现同时承担了ETagTokenFactory的任务。如此,我们简单的将被请求的URL的修改次数作为标记返回。

public String getToken(HttpServletRequest request) {
 String view= request.getRequestURI();
 Integer count= counts.get(view);
 if (count== null ) {
 return null ;
 }

 return count.toString();
 } 


就这样!

讨论

在这里,我们的拦截器将在没有相关数据发生变化时阻止一切收集数据和渲染页面的处理过程。现在,让我们来看一下HTTP头,以及在表象之下到底发生了些什么。示例程序中包含了使得owner.htm使用ETag的配置介绍。
第一次请求说明用户已经看到了该页面:

http://localhost:8080/petclinic/owner.htm?ownerId=10 

 GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
 Host: localhost:8080
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
 Accept-Language: en-us,en;q=0.5
 Accept-Encoding: gzip,deflate
 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
 Keep-Alive: 300
 Connection: keep-alive
 Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
 X-lori-time-1: 1182364348062
 If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT
 If-None-Match: "-1"

 HTTP/1.x 304 Not Modified
 Server: Apache-Coyote/1.1
 Date: Wed, 20 Jun 2007 18:32:30 GMT

下面我们触发一些变化,并观察ETag是否改变。为这个Owner增加了一个Pet:


----------------------------------------------------------
 http://localhost:8080/petclinic/addPet.htm?ownerId=10

 GET /petclinic/addPet.htm?ownerId=10 HTTP/1.1
 Host: localhost:8080
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
 Accept-Language: en-us,en;q=0.5
 Accept-Encoding: gzip,deflate
 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
 Keep-Alive: 300
 Connection: keep-alive
 Referer: http://localhost:8080/petclinic/owner.htm?ownerId=10
 Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
 X-lori-time-1: 1182364356265

 HTTP/1.x 200 OK
 Server: Apache-Coyote/1.1
 Pragma: No-cache
 Expires: Thu, 01 Jan 1970 00:00:00 GMT
 Cache-Control: no-cache, no-store
 Content-Type: text/html;charset=ISO-8859-1
 Content-Language: en-US
 Content-Length: 2174
 Date: Wed, 20 Jun 2007 18:32:57 GMT


 ----------------------------------------------------------
 http://localhost:8080/petclinic/addPet.htm?ownerId=10 


 
 POST /petclinic/addPet.htm?ownerId=10 HTTP/1.1
 Host: localhost:8080
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
 Accept-Language: en-us,en;q=0.5
 Accept-Encoding: gzip,deflate
 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
 Keep-Alive: 300
 Connection: keep-alive
 Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10
 Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
 X-lori-time-1: 1182364402968
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 40
 name=Noddy&birthDate=1000-11-11&typeId=5
 HTTP/1.x 302 Moved Temporarily
 Server: Apache-Coyote/1.1
 Pragma: No-cache
 Expires: Thu, 01 Jan 1970 00:00:00 GMT
 Cache-Control: no-cache, no-store
 Location: http://localhost:8080/petclinic/owner.htm?ownerId=10
 Content-Language: en-US
 Content-Length: 0
 Date: Wed, 20 Jun 2007 18:33:23 GMT

因为我们没有为addPet.htm配置ETag,所以不设置相关的响应头。现在,我们再次访问Owener 10,注意相应中的ETag成为了1:


----------------------------------------------------------
 http://localhost:8080/petclinic/owner.htm?ownerId=10

 GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
 Host: localhost:8080
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
 Accept-Language: en-us,en;q=0.5
 Accept-Encoding: gzip,deflate
 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
 Keep-Alive: 300
 Connection: keep-alive
 Referer: http://localhost:8080/petclinic/addPet.htm?ownerId=10
 Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
 X-lori-time-1: 1182364403109
 If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT
 If-None-Match: "-1"

 HTTP/1.x 200 OK
 Server: Apache-Coyote/1.1
 Etag: "1"
 Last-Modified: Wed, 20 Jun 2007 18:33:36 GMT
 Content-Type: text/html;charset=ISO-8859-1
 Content-Language: en-US
 Content-Length: 4317
 Date: Wed, 20 Jun 2007 18:33:45 GMT

最后,我们再次请求Owener 10,这次ETag起了作用,我们接受到了一个304未改变信息。

----------------------------------------------------------
 http://localhost:8080/petclinic/owner.htm?ownerId=10

 GET /petclinic/owner.htm?ownerId=10 HTTP/1.1
 Host: localhost:8080
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
 Accept-Language: en-us,en;q=0.5
 Accept-Encoding: gzip,deflate
 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
 Keep-Alive: 300
 Connection: keep-alive
 Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
 X-lori-time-1: 1182364493500
 If-Modified-Since: Wed, 20 Jun 2007 18:33:36 GMT
 If-None-Match: "1"

 HTTP/1.x 304 Not Modified
 Server: Apache-Coyote/1.1
 Date: Wed, 20 Jun 2007 18:34:55 GMT

如此,我们使用HTTP缓存降低了带宽占用,缩短了处理周期。
The Fine Print: 事实上,采用更细粒度的对象变化跟踪,例如使用对象标识。可以更大程度的提高效率。但是,页面和实体之间的关联很大程度上是由系统中的数据模型设计决定的。上面的实现(ModifiedObjectTracker)是一个说明性的例子,谜底是为更深入的尝试提供思路。上面的实现的目的不是应用于实际的生产环境中(例如不适用于集群环境),一种更远的考虑是使用数据库的触发器跟踪数据变化,让拦截器监测触发器输出结果所在的数据表。

结论

我们已经看到了使用ETag降低贷款占用和缩短处理周期的两种方法。我所希望的是这篇文章为你现在和将来的Web应用项目提供了一种思路,以及对底层的ETag响应头的正确理解和使用。
正如牛顿所说,“如果我看得更远,那是因为我站在巨人的肩膀上”。作为REST的核心,这种风格的应用程序讲的是简单、优雅的软件设计,不重复发明轮子。我相信了解和使用REST风格的架构的核心是主流应用程序开发的一个好的发展,并且我盼望着在以后的开发中能够抬起它的未来。

2
1
分享到:
评论

相关推荐

    SSH+REST 最小版完整jar包,已验证,正常启动

    在lib目录下,很可能包含了所有必要的第三方依赖库,这些库支持SSH连接、REST服务处理和Struts 2框架的运行。 为了使用这个jar包,你需要有一个Java运行环境(JRE),然后可以使用`java -jar`命令来启动这个应用...

    Node.js-WebSSH2基于Web的SSH2客户端使用xterm.jssocket.io和ssh2实现

    在本项目中,"Node.js-WebSSH2基于Web的SSH2客户端使用xterm.js、socket.io和ssh2实现",我们关注的是构建一个在浏览器环境中运行的SSH2客户端。这个客户端允许用户通过Web界面安全地连接到远程服务器执行命令,从而...

    Linux基础 电子教材-08-Linux中的ssh服务和web服务.pdf

    【SSH服务】 SSH(Secure Shell)是Linux系统中用于实现远程安全登录的一种协议,它确保了数据传输的安全性,防止了数据被窃取或篡改。SSH2是目前广泛使用的版本,提供了更好的安全性。SSH的核心在于对称加密和非...

    基于SSH的WEB开发

    在现代Web开发中,SSH(Struts + Hibernate + Spring)是一种常见的开发框架组合,它提供了强大的功能和灵活性,用于构建高效、可维护的Web应用程序。下面我们将深入探讨SSH的各个组成部分,以及它们如何协同工作来...

    ssh开发小型web网站

    SSH开发小型Web网站是Java开发领域中的一个常见话题,它主要涉及到Spring、Struts和Hibernate这三个框架的集成使用。这三大框架的组合被广泛应用于构建高效、可维护的Web应用程序,尤其适合小型项目。下面我们将深入...

    Python-WebSSH一个基于Web的ssh客户端

    1. **基于Python**: Python-WebSSH使用Python编程语言进行开发,这使得它具有跨平台的特性,可以在多种操作系统上运行。 2. **Tornado框架**: Tornado是一个高性能、异步网络库,用于构建高并发的Web应用。在Python...

    基于SSH框架的web项目

    【基于SSH框架的Web项目】是一个综合性的开发实践,它主要使用了Struts2、Spring和Hibernate这三大流行开源框架,构建了一个功能完善的Web应用程序。SSH框架组合在Java Web开发中广泛使用,提供了模型-视图-控制器...

    ssh+cxf webservices完整版

    这个项目提供了完整的源码和必要的库文件(jar包),旨在帮助开发者快速理解和部署一个使用CXF进行Web服务开发的示例。下面我们将详细探讨这些关键技术和在项目中的应用。 1. **Spring**:Spring是一个流行的Java...

    ssh_web开发测试程序学习笔记

    这个“ssh_web开发测试程序学习笔记”涵盖了使用这三个框架进行Web开发和测试的基本概念、步骤和最佳实践。 Struts2是MVC(Model-View-Controller)设计模式的一个实现,它提供了强大的表单处理、国际化、异常处理...

    webssh远程登录

    【WebSSH远程登录详解】 WebSSH是一种基于Web的SSH客户端,它允许用户通过网页界面远程登录并管理Linux服务器,提供了一种便捷、安全的访问方式,尤其适合在跨设备或公共网络环境中操作远程主机。本篇文章将深入...

    web开发的ssh开发包

    在Web开发领域,SSH(Struts2、Spring、Hibernate)是一个经典的开源框架组合,用于构建高效、可扩展的企业级应用程序。这些框架各自扮演着不同的角色,共同助力开发者实现灵活、松耦合的软件架构。 Struts2是MVC...

    login-with-ssh, 使用 SSH http验证web会话的实验.zip

    login-with-ssh, 使用 SSH http验证web会话的实验 使用SSH登录的这里是演示。这是一个简单的通过SSH验证网络会话的实验。 这样做可以为你提供完全分散的。无密码的认证 !工作原理定制的SSH服务器监听连接。 它不是...

    java WEB SSH框架整合详解

    ### Java WEB SSH框架整合详解 在Java Web开发领域,SSH框架(Struts、Spring、Hibernate)的整合使用是提升项目效率、代码质量和可维护性的关键。本文将详细解析SSH框架的整合过程,从环境搭建到具体操作步骤,...

    WEB开发-SSH三层框架

    在IT行业中,SSH(Struts2 + Spring + Hibernate)是一个非常经典的Java Web开发框架,用于构建高效、可扩展的企业级应用程序。这个“WEB开发-SSH三层框架”项目涵盖了这三个核心组件,以及相关的数据库文件和工程...

    ubuntu下ssh配置详解

    Ubuntu 下 SSH 配置详解 在 Linux 的 Ubuntu 版本下,配置 SSH 服务是一件非常重要的事情。SSH(Secure Shell)是一种安全的远程登录协议,可以实现远程登录到服务器上。下面将详细介绍在 Ubuntu 下配置 SSH 服务的...

    SSH开发指SSH开发指

    SSH开发指SSH开发指SSH开发指SSH开发指SSH开发指

    sshjar包SSH开发

    SSH开发是指基于Spring、Struts和Hibernate这三个开源框架的Java Web应用程序开发模式。这三大框架分别负责业务逻辑层(Spring)、视图层(Struts)和数据持久化层(Hibernate)。在进行SSH开发时,正确地配置和选择...

    人人都玩开心网:Ext+JS+Android+SSH整合开发Web与移动SNS

    《人人都玩开心网:Ext+JS+Android+SSH整合开发Web与移动SNS》这本书主要聚焦于构建社交网络服务(SNS)平台,通过结合多种技术实现Web端和移动端的应用开发。以下是书中涉及的主要知识点: 1. **EXT.JS**: EXT....

Global site tag (gtag.js) - Google Analytics