java程序设计之网络编程,java高并发与网络编程实战
作者:库赛马哈茂德(2002年11月14日)
译者:《边城的疯子》(2002年12月10日)
来源:边城客栈
-
基于HTTP的应用程序
java.net包中的类和接口提供了可用于低级和高级网络编程的API。低级API允许您直接访问网络协议,但是您必须使用低级TCP套接字和UDP数据包来实现此目的。高层API(如URL、URLConnection、httpURLConnection等。)可以让你更快的开发网络应用,但是不需要写很多代码。
另一篇文章《Network Programming with J2SE 1.4》会告诉你如何使用低级套接字进行网络编程。本文主要讨论如何使用java.net包中的高级API来开发基于HTTP的应用程序。
这篇文章将有以下内容:
概述HTTP
java.net包高层API概述
示例展示了如何使用高级API。
做一个可以下载股票行情的应用。
演示如何向web服务器提交数据。
HTTP验证概述并展示如何保护您的网络资源。
提供代码示例来演示如何执行HTTP身份验证。
概述HTTP
超文本传输协议(HTTP)是一种请求-响应应用协议。该协议支持一组固定的方法,如GET、POST、PUT、DELETE等。使用常规GET方法向服务器请求资源。下面是GET请求的两个示例:
GET/HTTP/1.1
GET /names.html HTTP/1.1
此外,您可以使用GET和POST方法向服务器发送数据。它们以不同的方式向服务器发送数据:
GET方法:输入的数据将作为URL的一部分发送。
POST方法:输入数据作为独立的实体发送。
考虑下面的HTML表单:
form action= http://www . javacourses . com/servlet/GET marks method= GET
学生编号:
输入类型=文本名称=数字大小=30
输入类型=提交名称=获取标记值=获取标记
/表单
该表单将提交给http://www.javacourses.com/Servlet/getMarks进行servlet处理。表单使用GET方法传输信息。如果用户输入学号?比如556677?并单击GetMarks按钮,表单数据将作为URL的一部分发送到Servlet。编码后的网址是:http://www.javacourses.com/servlets/getMarks?号码=556677。
在使用POST方法的情况下,在传输数据时,数据不会被用作URL的一部分;它们将作为一个独立的实体进行传输。所以POST方式更安全,也可以用这种方式传输更多的数据。而且POST传输的数据不一定是文本,GET方法传输的数据一定是文本。
消息格式
请求消息指定方法名(GET或POST)、URL、协议版本号、头消息和可选消息。报头可能包含请求信息和客户端信息,例如接受的内容类型、浏览器名称和认证数据。返回消息指定协议版本、响应代码和原因。无论执行是否成功,都会报告响应代码和原因。一些响应代码如下:
200 OK:请求成功。请求的资源可以在稍后的消息中找到。
301永久移动:请求的资源已经移动。新位置将在此消息的后面指定。
400错误请求:服务器不理解请求消息。
404找不到:在此服务器上找不到请求的文档。
关于HTTP和所有返回代码的信息可以在HTTP 1.1规范RFC2616中找到。
下面是一个从浏览器到服务器的请求消息的例子。此处请求的URL是http://java.sun.com:
GET/HTTP/1.1
接受:image/gif,image/x-xbitmap,image/jpeg,image/pjpeg,
应用程序/vnd.ms-powerpoint,应用程序/vnd.ms-excel,
应用程序/msword,*/*
接受语言:英语
接受编码:gzip,deflate
用户代理:Mozilla/4.0(兼容;MSIE 5.01视窗98;YComp 5.0.0.0)
主持人:java.sun.com
连接:保持活动状态
cookie:SUN _ ID=24 . 80 . 19 . 177:28346100732290;
SunONEUserId=24 . 80 . 19 . 177:86525219607767
下面是服务器对此请求的回复消息:
HTTP/1.1 200没问题
服务器:网景企业版/6.0
日期:2002年10月14日星期一15:18:04 GMT
内容类型:文本/html
连接:关闭
java.net包高层API概述
java.net包包含高级API。它们实现了一些最常用的基于TCP的协议,比如HTTP和FTP。两个主要的类是URL和URLConnection。另一个有用的类是HttpURLConnection,它是URLConnection的子类,支持HTTP的特性。
URL(统一资源定位器)是描述文档(或其他公共资源)在互联网中的位置的地址。URL看起来像这样:
协议://机器名:端口/资源
需要注意的是,URL类不是基于HTTP的。它支持FTP,HTTPS和文件协议。因此,对于URL类,以下所有URL都是有效的。
http://java.sun.com
http://本地主机:8080/我的应用程序
http://www.yahoo.com/index.html
http://www.yahoo.com
ftp://ftp.borland.com
ftp://ftp.sun.com
https://www.scotiaonline.scotiabank.com
https://central.sun.net
file:///C:/j2sdk 1.4/docs/API/index . html
当您在浏览器中输入URL时,浏览器会生成一个HTTP GET(或POST)命令来查找(或查询)URL所请求的资源。如果未指定要查询的资源,将查询默认文档(通常为index.html)。
阅读URL的内容
让我们从一个简单的应用程序开始,它将直接从URL读取内容。先尝试用低级套接字读取。请参见示例代码1。在本例中,用户在命令行输入资源的URL,然后在端口80(默认的HTTP服务器端口号)打开一个套接字,并建立相应的iostream。输出流用于向HTTP服务器发送HTTP命令(比如GET ),而输入流用于从HTTP服务器读取反馈。注意,在这个例子中,服务器响应的头信息也将被读入(这不是URL内容)。
例子1:ReadURL1.java
导入Java . net . *;
导入Java . io . *;
公共类ReadURL1 {
公共静态void main(String argv[])引发异常{
final int HTTP _ PORT=80
if(argv.length!=1) {
用法:java ReadURL1 url
system . exit(0);
}
Socket socket=new Socket(argv[0],HTTP _ PORT);
BufferedWriter输出
=new buffered writer(new output streamwriter(socket . get output stream()));
BufferedReader在
=new buffered reader(new InputStreamReader(socket . getinputstream()));
out . write( GET/index . html HTTP/1.0 );
out . flush();
弦线;
string buffer sb=new string buffer();
while((line=in.readLine())!=null) {
sb .追加(行);
}
out . close();
in . close();
system . out . println(sb . tostring());
}
}
//结束代码
当您运行此示例时,请注意URL必须是域名,如java.sun.com或IP地址。您不能将http://添加为URL的一部分。如果您想解决这个问题,您必须解析输入并找出它使用什么协议、端口号和请求什么资源。也可以使用URL类,它提供了一些非常有用的方法,比如getProtocal、getPort、getHost和getFile。
使用URL类
要从URL读取内容,可以使用URL类轻松实现,如示例代码2所示。这使得直接从URL读取内容变得容易。以这种方式读取的内容不包含服务器响应的头信息,所以不需要解析它们。通过输入一个有效的URL来运行这个例子,比如protocol://domain name:port/resource。URL类解析输入的URL,处理底层麻烦的工作。
例子2:ReadURL2.java
导入Java . net . *;
导入Java . io . *;
公共类ReadURL2 {
公共静态void main(String argv[])引发异常{
if(argv.length!=1) {
用法:java ReadURL2 url
system . exit(0);
}
URL url=新URL(argv[0]);
BufferedReader在
=new buffered reader(new InputStreamReader(URL . openstream()));
弦线;
string buffer sb=new string buffer();
while ((line=in.readLine())!=null) {
sb .追加(行);
}
in . close();
system . out . println(sb . tostring());
}
}
在你运行ReadURL2的时候,你会看到你输入的统一资源定位器所请求的文档内容命令窗口中显示出来。
如果你想做除了读取之外的其它事情,请使用打开连接来建立到统一资源定位器的连接。这个方法返回一个通过对象,你可以用这个对象来与统一资源定位器通信,如读、写、查询等。一但开放连接方法创建了连接,你就可以使用获取内容类型、获取内容长度、获取内容编码等非常有用的方法了。
使用通过类
我们现在要创建一个从http://quote.yahoo.com获取股票信息的应用程序来演示如何使用URLConnection。为了获取特别的股票信息,用户输入股票标号(比如SUNW、IBM或者MOT),由应用程序从雅虎报价服务器获取相应的股票信息。应用程序会显示出该股票的名称、价格和日期。
有两种方法(可以用在这个应用程序中的)用来从雅虎报价服务器获取股票信息。第一种方法的格式如下:
http://quote.yahoo.com/d/quotes.csv?s=SUNW f=slc1wop
如果你在浏览器的地址栏输入这个URL,你将会看到像图一所示的那些内容。
屏幕。宽度-333)这个。宽度=屏幕。宽度-333;
图1:获取股票行情
另一种方法的格式是:
http://finance.yahoo.com/q?s=SUNW
屏幕。宽度-333)这个。宽度=屏幕。宽度-333;
图2: 获取股票行情的另一种方法
你可以看到第一种方法的结果是一行文本,解析时会比第二种容易许多,也快许多,因为第二种方法的结果含有大量的文字,包括许多广告和格式化信息。所以我用第一种方法实现这个关于股票的应用程序,它由两个类组成:Stock.java和StockReader.java。
Stock.java类
这个类将从雅虎报价服务器获得的字符串解析成字段(例如股票名称、价格和日期)。示例代码3中展示了一个实现的例子。示例代码四中的股票阅读器类会用到这个工具类。
示例代码3:Stock.java
公共类别股票{
私有静态字符串名称、时间、价格;
//给定来自服务器的报价,
//检索股票的名称、价格和日期
公共静态空的解析(字符串数据){
int index=data。的索引();
name=data.substring( index,(index=data.indexOf(,index)));
指数=3;
time=data.substring(index,(index=data.indexOf( -,index))-1);
指数=5;
price=data.substring(index,(index=data.indexOf(,index)));
}
//从获取股票的名称
公共静态字符串getName(字符串记录){
解析(记录);
返回(姓名);
}
//从获取股票价格
公共静态字符串getPrice(字符串记录){
解析(记录);
退货(价格);
}
//获取股票的日期
公共静态字符串获取日期(字符串记录){
解析(记录);
返回时间;
}
}
StockReader.java类
这个类的任务是连接到雅虎报价服务器,并从服务器上获取股票行情。它使用股票类解析从服务器返回的字符串。示例代码四是它的一个实现。
示例代码4:StockReader.java
导入Java。io。*;
导入Java。网。*;
公共类库存阅读器{
公共静态void main(String argv[])引发异常{
String quoteFormat= f=slc1wop
if (argv.length!=1) {
用法:java股票阅读器符号
系统。出口(1);
}
URL url=新网址( http://引用。雅虎。com/d/quotes。CSV?);
URL连接连接=URL。打开连接();
联系。setdoooutput(true);
PrintWriter out=新的PrintWriter(连接。获取输出流());
出去。println( s= argv[0]quote格式);
出去。close();
BufferedReader in=新BufferedReader(
新建InputStreamReader(连接。getinputstream());
字符串line=in。readline();
/*调试
while ((line=in.readLine())!=null) {
系统。出去。println( Got: line );
}
*/
英寸close();
系统。出去。println( Name: stock。getname(line));
系统。出去。println( Price:股票。getprice(line));
系统。出去。println(日期:股票。getdate(line));
}
}
将数据提交到网页服务器
在上面的例子中,使用GET方法将数据作为URL的一部分发送到服务器。现在让我们看一个使用POST方法发送数据的例子。在这个例子中,http://www.javacourses.com/cgi-bin的CGI脚本(名为。cgi)需要名称和电子邮件值。如果用户提交莎莉麦克唐纳作为姓名值,smc@yahoo.com作为电子邮件值,CGI脚本将获得输入,解析和解码消息,然后将提交的内容返回给客户端。这个CGI脚本没有做太多事情,但是我们将使用它来演示如何向服务器提交数据。
还有一点很重要?请注意,当使用POST方法时,消息内容的类型是application/x-www-form-urlencoded,这将:
指定常规数据编码
将空格转换为加号()
将非文本内容转换为后跟百分号(%)的十六进制数。
在每个名称=值对之间放置一个符号
根据此编码规则,消息(姓名=莎莉麦克唐纳和email=smc@yahoo.com)必须编码为:
莎莉麦克唐纳email=smc@yahoo.com
CGI将在接收到这个编码消息后对其进行解码。然而,幸运的是,您不必手动编码。可以使用java.net.URLEncoder类对消息进行编码,如下例所示。相应地,您可以使用java.net.URLDecoder对消息进行解码。
这个例子的实现(使用HttpURLConnection类)如代码5所示,它展示了如何使用POST方法向服务器发送数据。你会看到:
为CGI脚本打开连接和I/O流
将请求方法设置为POST。
使用URLEncoder.encode方法对消息进行编码(URLDecoder.decode方法可用于解码)
将编码的消息发送到CGI脚本
接收服务器返回的消息,并在控制台上打印出来。
例子5:PostExample.java
导入Java . io . *;
导入Java . net . *;
公开课后示例{
公共静态void main(String[] argv)引发异常{
URL URL=new URL( http://www . javacourses . com/CGI-bin/names . CGI );
HttpURLConnection连接=(HttpURLConnection)URL . open connection();
connection . setrequestmethod( POST );
connection . setdoooutput(true);
PrintWriter out=new PrintWriter(connection . get output stream());
//编码消息
string name= name= urlencoder . encode( Qusay Mahmoud , UTF-8 );
string email= email= URL encoder . encode( qmahmoud @ javacourses . com , UTF-8 );
//发送编码的消息
out.println(姓名 电子邮件);
out . close();
BufferedReader在
=new buffered reader(new InputStreamReader(connection . getinputstream()));
弦线;
while ((line=in.readLine())!=null) {
system . out . println(line);
}
in . close();
}
}
服务器和防火墙
如果你使用防火墙,你必须告诉Java代理服务器的详细信息和端口号,这样你就可以访问防火墙外的主机。您可以通过定义一些HTTP或FTP属性来做到这一点:
http.proxyHost(默认值:无)
http.proxyPort(如果指定了http.proxyHost,则默认值为80)
http.nonProxyHosts(默认值:无)
Http.proxyHost和http.proxyPort用于指定Http协议处理器需要使用的代理服务器和端口号。Http.nonProxyHosts用于指定哪些主机是直接连接的(即不通过代理服务器连接)。http.nonProxyHosts属性的值是用分隔的主机列表,可以用正则表达式表示匹配的主机,比如:*.sfbay.sun.com将匹配sfbay域中的任何主机。
ftp.proxyHost(默认值:无)
ftp.proxyPort(如果指定了ftp.proxyHost,则默认值为80)
ftp.nonProxyHosts(默认值:无)
Ftp.proxyHost和ftp.proxyPort用于指定Ftp协议处理器需要使用的代理服务器和端口号。Ftp.nonProxyHosts用于指定直接联系哪些主机,指定的方法类似于http.nonProxyHosts。
您可以在应用程序启动时设置这些属性:
prompt Java-dhttp . proxy host=HostName-dhttp . proxy port=port number your app
HTTP认证
HTTP协议提供了保护资源的认证机制。当一个请求请求受保护的资源时,web服务器用401未授权错误代码来响应。该响应包含一个WWW-Authenticate头,它指定了身份验证方法和域。可以将这个域想象成一个包含用户名和密码的数据库,这些用户名和密码将用于识别受保护资源的有效用户。例如,如果您尝试访问网站上标记为“个人文件”的资源,服务器的响应可能是:www-authenticate:basicrealm= Personal Files (假设身份验证方法是基本的)。
验证技术
现在网络应用的认证方法有几种,其中最广泛使用的是基本认证和摘要认证。
当用户想要访问有限的资源时,使用基本身份验证方法的web服务器将要求浏览器显示一个对话框,并要求用户输入用户名和密码。如果用户的用户名和密码正确,服务器将允许他访问这些资源;否则,在连续三次尝试失败后,将显示错误消息页面。这种方式的缺点是用户名和密码都是用Base64编码的(都是可读文本),然后传输。也就是说,这种认证方式只是和Telnet一样安全,而不是很安全。
数据认证方式不在网络中传输密码,而是生成一些数字(根据密码和其他需要的数据生成)来代替密码,这些数字通过MD5(消息摘要算法)加密。生成的值与服务器所需的其他信息一起传输,以在网络中更正密码。这种方法显然更安全。
基于表单的身份验证方法类似于基本身份验证方法,只是服务器使用您的自定义登录页面而不是标准登录对话框。
最后,客户端证书验证使用SLL(安全套接字层)和客户端证书。
在Tomcat下保护资源
您可以在tomcat-users.xml文件中编写用户及其角色的列表。该文件位于TOMCAT_HOME下的conf目录中(安装TOMCAT的目录)。默认情况下,该文件包含三个用户的定义(tomcat、role1、两者)。下面的XML代码是我添加了两个新用户(qusay和reader)后的tomcat-users.xml:
tomcat用户
用户名=tomcat 密码=tomcat 角色=tomcat /
用户名=role1 密码=tomcat 角色=role1 /
用户名=both 密码=tomcat 角色=tomcat,role1 /
用户名=qusay 密码=guesswhat 角色=作者/
用户名=reader 密码=youguess 角色=reader /
/Tomcat-用户
两个新添加的用户(qusay和reader)的角色分别被设置为author和reader。属性非常重要,因为当您创建安全规则时,每个受限资源都与一个可以访问它的角色相关联(您将在后面看到)。
让我们做一个实验(假设你已经安装并配置了Tomcat)。为您想要的页面应用程序创建一个目录。您可以通过以下步骤进行准备:
在安装Tomcat的目录下,有一个名为webapps的目录。在这个目录下创建一个目录(比如:learn)。
在第一步中创建的目录下创建一个子目录,并将其命名为chapter。
在章节目录中,创建一个包含自定义内容的HTML文件,文件名为index.html。
在第一步中创建的目录下创建一个名为WEB-INF的子目录。
在WEB-INF目录中创建一个名为web.xml的文件,其内容如下:
?xml版本=1.0 编码=ISO-8859-1 ?
!DOCTYPE网络应用
PUBLIC -//Sun Microsystems,Inc.//DTD Web Application 2.3//EN
http://Java . sun . com/dtd/we B- app _ 2 _ 3 . dtd
元素内
描述
学习Web编程
/描述
安全约束
网络资源收集
网络资源名称
禁区
/web-resource-name
URL-模式/章节/*/URL-模式
/web-资源-收藏
授权约束
角色名Tomcat/角色名
角色名作者/角色名
角色名读取器/角色名
/授权约束
/安全约束
登录配置
基本验证方法/验证方法
领域名称验证您自己/领域名称
/登录配置
/网络应用
Web.xml配置描述
Web.xml是描述配置的文件。这里,我们将重点关注与安全相关的配置元素。
Security-constraint:该元素限制对一个或多个资源的访问,并且可以在配置信息中多次出现。在上面的配置信息中,限制了chapter目录下所有资源的访问(http://localhost:8080/learn/chapter)。安全约束包含以下元素:
We-resource-collection:这个元素用于标识您想要限制访问的资源。可以定义URL模式和HTTP方法(用http-method元素定义HTTP方法)。如果没有定义HTTP方法,该限制将适用于所有方法。在上面的应用程序中,我想限制访问的资源是http://localhost:8080/learn/chapter/*,即chapter目录中的所有文档。
Auth-constraint:该元素可以访问上面定义的受限资源的用户角色。在上面的应用程序中,tomcat、author和erader可以访问这些资源。
Login-config:这个元素用于指定认证方法。它包含以下元素:
Auth-method:指定验证方法。它的值可以是下列值集之一:BASIC(基本身份验证)、DIGEST(摘要身份验证)、FORM(基于表单的身份验证)或CLIENT-CERT(客户证书身份验证)。
Realm-name:如果基本方法用于验证,则是标准登录对话框中的描述性名称。
例子
上述配置中使用了基本身份验证方法。我们来做一个实验:启动你的Tomcat服务器,给它发送一个请求到http://localhost:8080/learn/chapter。这时,会出现一个如图3所示的对话框,提示您输入用户名和密码:
screen . width-333)this . width=screen . width-333;
图3: HTTP基本认证
输入一个用户及其密码(可以查看tomcat-users.xml文件)。该用户的角色应该存在于配置(web.xml)中。如果您输入了正确的用户名和密码,就可以访问这些资源;否则,你可以再试一次。
实验抽象验证:
关闭你的Tomcat服务器。
修改您的配置文件(web.xml)并将BASIC更改为DIGEST。
重新启动Tomcat服务器。
打开一个新的浏览器窗口。
在地址栏中输入http://localhost:8080/learn/chapter并输入。
您将看到一个类似的对话框。从图4中可以看出,这个登录对话框是安全的,因为使用了摘要验证。
screen . width-333)this . width=screen . width-333;
图4: HTTP摘要认证
服务器的后台回复
当使用基本身份验证方法来保护资源时,服务器会发回类似于图5所示的响应消息:
screen . width-333)this . width=screen . width-333;
图5:服务器回复(基本验证)
如果是受的摘要式身份验证方法保护的资源,服务器发回的响应信息如图6所示:
screen . width-333)this . width=screen . width-333;
图6:服务器回复(摘要验证)
支持Java HTTP认证
J2SE (1.2版本1.2或更高版本)通过Authenticator类为身份验证提供本地支持。您所要做的就是继承这个类并实现它的getPasswordAuthentication方法。此方法获取用户名和密码,并使用它们生成要返回的PasswordAuthentication对象。之后,您必须使用Authenticator.setDefault方法注册您的Authenticator实例。现在,每当您想要访问受保护的资源时,您将调用getPasswordAuthentication。Authenticator类管理所有底层细节。它不受HTTP的限制,可以应用于所有网络连接,这是一个好消息。
代码6展示了一个实现Authenticator的示例。可以看到,请求认证时,getPasswordAuthentication方法会弹出一个登录对话框。
例子6:AuthImpl.java
导入Java . net . *;
导入Java . awt . *;
导入javax . swing . *;
公共类AuthImpl扩展验证器{
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。