最近碰到的问题,客户端调用本地的扫描仪,将扫描的文件上传。
使用到的技术:applet,twain,HttpClient
当然也碰到很多问题,因为在这周之前我都不知道什么是applet
一.Applet操作本地资源
基于安全方面的原因,applet是不允许操作本地资源的。但是java提供了相应的为jar包签名的机制来提升applet的权限。相信很多人都 碰到过这种对话框:
![](http://hiphotos.baidu.com/sartre/pic/item/42a7d933e795147fad4b5f58.jpg)
让用户来决定是否给applet提升权限,如果用户信任这个资源,applet将能操作本地资源。
1.为applet依赖的jar包签名
java提供了两个工具;
keytool用于生成存放key的库
jarsigner用于为jar进行签名
首先建立一个keystore(这是在当前路径上操作):
keytool -genkey -alias zengge -keystore zengge.keystore
keytool -genkey表示建库
-alias zengge是为要建立的key取一个别名
-keystore zengge.keystore是建立一个名字叫zengge.keystore的key库,key就存在里面.
如下图:
![](http://hiphotos.baidu.com/sartre/pic/item/c895d1439c4b142f73f05d58.jpg)
这样一个keystore就建立好了,在当前目录下就多出了一个zengge.keystore的文件
![](http://hiphotos.baidu.com/sartre/pic/item/ad4bd11340f0473e5aaf5358.jpg)
有了keystore之后jarsigner就可以利用存放在keystore中的key来为jar签名
![](http://hiphotos.baidu.com/sartre/pic/item/73f082026faf61334bfb5158.jpg)
这里有两个类,先打包
![](http://hiphotos.baidu.com/sartre/pic/item/5aafa40f7cfb3ed1ab645758.jpg)
包里面的META-INFO里面只有一个文件,且内容为上图
接下来为jar包签名:
jarsigner -keystore zengge.keystore HelloWorld.jar zengge
zengge.keystore是keystore的路径/名字,这里是相对路径
HelloWorld.jar是要签名的jar包
zengge是存放在keystore中的密钥的别名
![](http://hiphotos.baidu.com/sartre/pic/item/4bfbfbed9264c67378f05558.jpg)
密码为建立keystore时的密码
再来看一下签过名的jar里面的情况有什么变化:
![](http://hiphotos.baidu.com/sartre/pic/item/ab64034f43f0320aafc3ab59.jpg)
可以看到META-INFO里面现在是三个文件,.DSAG与.SF我想肯定是用来加解密用的,大家注意现在的MANIFEST.MF,
可以看出为每个类添加了一个SHA签名,用它来保证,这个jar里面的内容不会被其它人修改,用户可以相信这个jar。
2.将applet嵌入html
applet是通过浏览器来运行了,可能你会问,java的东西浏览器怎么能运行呢,难道客户端也要安装java?
实际上applet是通过嵌在浏览器中的jvm在运行,但是这个jvm是从那里来的呢?
对于IE,大家可以看一下,下图中的java如果选中,就表示会调用本地的jre来运行applet。
![](http://hiphotos.baidu.com/sartre/pic/item/78f0f73692c3bc0d0a55a959.jpg)
但是绝大部分情况下,客户端是不会安装java的啊,还有firefox下面没有类似的这种选项。那么要如何来解决这种问题呢?
早期的applet都是用applet标签来嵌入html的(当然现在也可以),例如:
<APPLET CODE = "HelloWorld" archive ="HelloWorld.jar" JAVA_CODEBASE = "." WIDTH = "320" HEIGHT = "240" NAME = "HelloWorld"></APPLET>
这里的CODE表示类名(类名后可以加上.class),archvie表示类所在的jar包,如果你有多个jar包,可以全加在archive里 面,用,号分开(archive="a,jar,b.jar,c.jar",当然这些jar要签名的还得签名)。
如果用这种标签,在IE下,如果没选中用本地jre运行applet的话,是运行不了的,没安装插件的firefox也是不能运行的。
面对这种情况,升级版的标签出现了,java提供了一个工具名字叫HTMLconverter,通过它,能将html中的applet标签转换成标 准的标签,如下
Html代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=%3Cobject%0A%20%20%20%20classid%20%3D%20%22clsid%3A8AD9C840-044E-11D1-B3E9-00805F499D93%22%0A%20%20%20%20codebase%20%3D%20%22http%3A%2F%2Fjava.sun.com%2Fupdate%2F1.6.0%2Fjinstall-6u14-windows-i586.cab%23Version%3D6%2C0%2C0%2C8%22%0A%20%20%20%20WIDTH%20%3D%20%22320%22%20HEIGHT%20%3D%20%22240%22%20NAME%20%3D%20%22HelloWorld%22%20%3E%0A%20%20%20%20%3CPARAM%20NAME%20%3D%20CODE%20VALUE%20%3D%20%22HelloWorld%22%20%3E%0A%20%20%20%20%3CPARAM%20NAME%20%3D%20CODEBASE%20VALUE%20%3D%20%22.%22%20%3E%0A%20%20%20%20%3CPARAM%20NAME%20%3D%20ARCHIVE%20VALUE%20%3D%20%22applet_test.jar%22%20%3E%0A%20%20%20%20%3CPARAM%20NAME%20%3D%20NAME%20VALUE%20%3D%20%22HelloWorld%22%20%3E%0A%20%20%20%20%3Cparam%20name%20%3D%20%22type%22%20value%20%3D%20%22application%2Fx-java-applet%3Bversion%3D1.6%22%3E%0A%20%20%20%20%3Cparam%20name%20%3D%20%22scriptable%22%20value%20%3D%20%22false%22%3E%0A%09%0A%09%2F%2F%E4%B8%8A%E9%9D%A2%E6%98%AF%E9%92%88%E5%AF%B9IE%0A%09%2F%2F%E4%B8%8B%E9%9D%A2%E7%9A%84embed%E6%98%AF%E9%92%88%E5%AF%B9firefox%0A%09%0A%20%20%20%20%3Ccomment%3E%0A%09%3Cembed%0A%20%20%20%20%20%20%20%20%20%20%20%20type%20%3D%20%22application%2Fx-java-applet%3Bversion%3D1.6%22%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20CODE%20%3D%20%22HelloWorld%22%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20JAVA_CODEBASE%20%3D%20%22.%22%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20ARCHIVE%20%3D%20%22applet_test.jar%22%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20NAME%20%3D%20%22HelloWorld%22%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20WIDTH%20%3D%20%22320%22%20%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20HEIGHT%20%3D%20%22240%22%0A%09%20%20%20%20scriptable%20%3D%20false%0A%09%20%20%20%20pluginspage%20%3D%20%22http%3A%2F%2Fjava.sun.com%2Fproducts%2Fplugin%2Findex.html%23download%22%3E%0A%09%20%20%20%20%3Cnoembed%3E%20%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fnoembed%3E%0A%09%3C%2Fembed%3E%0A%20%20%20%20%3C%2Fcomment%3E%0A%3C%2Fobject%3E%0A%3C!--%0A%3CAPPLET%20CODE%20%3D%20%22HelloWorld%22%20JAVA_CODEBASE%20%3D%20%22.%22%20WIDTH%20%3D%20%22320%22%20HEIGHT%20%3D%20%22240%22%20NAME%20%3D%20%22HelloWorld%22%3E%0A%3C%2FAPPLET%3E%0A--%3E%0A%3C!--%22END_CONVERTED_APPLET%22--%3E" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf">
- <object
- classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
- codebase = "http://java.sun.com/update/1.6.0/jinstall-6u14-windows-i586.cab#Version=6,0,0,8"
- WIDTH = "320" HEIGHT = "240" NAME = "HelloWorld" >
- <PARAM NAME = CODE VALUE = "HelloWorld" >
- <PARAM NAME = CODEBASE VALUE = "." >
- <PARAM NAME = ARCHIVE VALUE = "applet_test.jar" >
- <PARAM NAMENAME = NAME VALUE = "HelloWorld" >
- <param name = "type" value = "application/x-java-applet;version=1.6">
- <param name = "scriptable" value = "false">
-
- // 上面是针对IE
- //下面的embed是针对firefox
-
- <comment>
- <embed
- type = "application/x-java-applet;version=1.6" \
- CODE = "HelloWorld" \
- JAVA_CODEBASE = "." \
- ARCHIVE = "applet_test.jar" \
- NAME = "HelloWorld" \
- WIDTH = "320" \
- HEIGHT = "240"
- scriptable = false
- pluginspage = "http://java.sun.com/products/plugin/index.html#download">
- <noembed>
- </noembed>
- </embed>
- </comment>
- </object>
- <!--
- <APPLET CODE = "HelloWorld" JAVA_CODEBASE = "." WIDTH = "320" HEIGHT = "240" NAME = "HelloWorld">
- </APPLET>
- -->
-
<object classid = "clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" codebase = "http://java.sun.com/update/1.6.0/jinstall-6u14-windows-i586.cab#Version=6,0,0,8" WIDTH = "320" HEIGHT = "240" NAME = "HelloWorld" > <PARAM NAME = CODE VALUE = "HelloWorld" > <PARAM NAME = CODEBASE VALUE = "." > <PARAM NAME = ARCHIVE VALUE = "applet_test.jar" > <PARAM NAME = NAME VALUE = "HelloWorld" > <param name = "type" value = "application/x-java-applet;version=1.6"> <param name = "scriptable" value = "false"> //上面是针对IE //下面的embed是针对firefox <comment> <embed type = "application/x-java-applet;version=1.6" \ CODE = "HelloWorld" \ JAVA_CODEBASE = "." \ ARCHIVE = "applet_test.jar" \ NAME = "HelloWorld" \ WIDTH = "320" \ HEIGHT = "240" scriptable = false pluginspage = "http://java.sun.com/products/plugin/index.html#download"> <noembed> </noembed> </embed> </comment> </object> <!-- <APPLET CODE = "HelloWorld" JAVA_CODEBASE = "." WIDTH = "320" HEIGHT = "240" NAME = "HelloWorld"> </APPLET> --> <!--"END_CONVERTED_APPLET"-->
其中Object部分是针对IE的,embed是针对firefox的.classid与codebase都是表示相应的plugin的下载地址, 如果codebase的版本高于classid将下载codebase版本的plugin。这样不管本地有没有安装java,applet都能正常运行 了。
现在,在applet里面己经能调用本地的资源了,那么如何驱动扫描仪呢?
二.applet调用TWain驱动本地扫描仪
TWain是一个标准,用于获取扫描仪等设备的信息,它有很多实现(基本都是收费的),这里选择的是一个开源twain产品,mmsc twain(官网 http://www.mms-computing.co.uk/例子很丰富)。
只要本地安装有扫描仪驱动,twain就能找到并运行且获取到扫描的数据。
它里面有个Scanner类,Scanner scanner = Scanner.getDevice()能获取到相应的设备。然后为scanner添加一个xxx,ScannerListener,它里面有个方法 public void update(ScannerIOMetadata.Type type, ScannerIOMetadata metadata){},第二个参数即为扫描得到的数据,而且这个方法是在扫描述的状态发生变变就会解发。具体可以看一些mmsc里面的例子。
三.applet与服务器通信
得到了扫描的数据,得把它上传到服务器。我这里用的是HttpClient(需要的jar包commons-codec- 1.4.jar,commons-httpclient-3.0.jar,commons-io-1.4.jar,commons-logging- 1.0.2.jar)。在使用时{zh0}对这几个jar包都签名(我没有测试这种方式,我是将这几个jar全给解压了,{zh1}连同我的类一起打成了一个jar, {zh1}签名),上传代码位于update方法内即可。
因为扫描仪有多种状态,所以要进行判断,那次才是拿到了扫描数据。
Java代码 <embed height="15" width="14" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" allowscriptaccess="always" quality="high" flashvars="clipboard=public%20void%20update(ScannerIOMetadata.Type%20type%2C%20ScannerIOMetadata%20metadata)%7B%0A%0A%20%20%20%20if(type.equals(ScannerIOMetadata.ACQUIRED))%7B%0A%20%20%20%20%20%20BufferedImage%20image%3Dmetadata.getImage()%3B%0A%20%20%20%20%20%20System.out.println(%22Have%20an%20image%20now!%22)%3B%0A%20%20%20%20%20%20HttpClient%20httpClient%20%3D%20new%20HttpClient()%3B%0A%20%20%20%20%20%20MultipartPostMethod%20mpm%20%3D%20new%20MultipartPostMethod(%22http%3A%2F%2Flocalhost%3A8086%2FReiyenDMS%2FTestUploadServlet%22)%3B%0A%2F%2F%20%20%20%20%20%20MultipartPostMethod%20mpm%20%3D%20new%20MultipartPostMethod(%22http%3A%2F%2Flocalhost%3A8086%2Fapplet_study%2Fservlet%2FAppletServlet%22)%3B%0A%20%20%20%20%20%20File%20file%20%3D%20new%20File(%22c%3A%2Fupload%2Fabc%22%2Bindex%2B%22.jpg%22)%3B%0A%20%20%20%20%20%20try%7B%0A%20%20%20%20%20%20%20%20ImageIO.write(image%2C%20%22jpg%22%2C%20file)%3B%0A%20%20%20%20%20%20%20%20index%2B%2B%3B%0A%20%20%20%20%20%20%20mpm.addParameter(%22aFile%22%2C%20%22haha.pdf%22%2C%20file)%3B%0A%09%09httpClient.executeMethod(mpm)%3B%0A%0A%20%20%20%20%20%20%7Dcatch(Exception%20e)%7B%0A%20%20%20%20%20%20%20%20e.printStackTrace()%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7Delse%20if(type.equals(ScannerIOMetadata.NEGOTIATE))%7B%0A%20%20%20%20%20%20ScannerDevice%20device%3Dmetadata.getDevice()%3B%0A%2F*%0A%20%20%20%20%20%20try%7B%0A%20%20%20%20%20%20%20%20device.setResolution(100)%3B%0A%2F%2F%20%20%20%20%20%20%20%20device.setRegionOfInterest(0.0%2C0.0%2C40.0%2C50.0)%3B%20%20%20%20%20%20%20%2F%2F%20top-left%20corner%2040x50%20mm%0A%20%20%20%20%20%20%20%20device.setRegionOfInterest(0%2C0%2C400%2C500)%3B%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20top-left%20corner%20400x500%20pixels%0A%20%20%20%20%20%20%20%20device.setShowUserInterface(false)%3B%0A%20%20%20%20%20%20%20%20device.setShowProgressBar(false)%3B%0A%20%20%20%20%20%20%7Dcatch(Exception%20e)%7B%0A%20%20%20%20%20%20%20%20e.printStackTrace()%3B%0A%20%20%20%20%20%20%7D%0A*%2F%0A%20%20%20%20%7Delse%20if(type.equals(ScannerIOMetadata.STATECHANGE))%7B%0A%20%20%20%20%20%20System.err.println(metadata.getStateStr())%3B%0A%20%20%20%20%7Delse%20if(type.equals(ScannerIOMetadata.EXCEPTION))%7B%0A%20%20%20%20%20%20metadata.getException().printStackTrace()%3B%0A%20%20%20%20%7D%0A%20%20%7D" src="http://www.javaeye.com/javascripts/syntaxhighlighter/clipboard_new.swf">
- public void update(ScannerIOMetadata.Type type, ScannerIOMetadata metadata){
-
- if(type.equals(ScannerIOMetadata.ACQUIRED)){
- BufferedImage image=metadata.getImage();
- System.out.println("Have an image now!");
- HttpClient httpClient = new HttpClient();
- MultipartPostMethod mpm = new MultipartPostMethod("http://localhost:8086/ReiyenDMS/TestUploadServlet");
-
- File file = new File("c:/upload/abc"+index+".jpg");
- try{
- ImageIO.write(image, "jpg", file);
- index++;
- mpm.addParameter("aFile", "haha.pdf", file);
- httpClient.executeMethod(mpm);
-
- }catch(Exception e){
- e.printStackTrace();
- }
- }else if(type.equals(ScannerIOMetadata.NEGOTIATE)){
- ScannerDevice device=metadata.getDevice();
-
-
-
-
-
-
-
-
-
-
-
- }else if(type.equals(ScannerIOMetadata.STATECHANGE)){
- System.err.println(metadata.getStateStr());
- }else if(type.equals(ScannerIOMetadata.EXCEPTION)){
- metadata.getException().printStackTrace();
- }
- }
public void update(ScannerIOMetadata.Type type, ScannerIOMetadata metadata){ if(type.equals(ScannerIOMetadata.ACQUIRED)){ BufferedImage image=metadata.getImage(); System.out.println("Have an image now!"); HttpClient httpClient = new HttpClient(); MultipartPostMethod mpm = new MultipartPostMethod("http://localhost:8086/ReiyenDMS/TestUploadServlet"); // MultipartPostMethod mpm = new MultipartPostMethod("http://localhost:8086/applet_study/servlet/AppletServlet"); File file = new File("c:/upload/abc"+index+".jpg"); try{ ImageIO.write(image, "jpg", file); index++; mpm.addParameter("aFile", "haha.pdf", file); httpClient.executeMethod(mpm); }catch(Exception e){ e.printStackTrace(); } }else if(type.equals(ScannerIOMetadata.NEGOTIATE)){ ScannerDevice device=metadata.getDevice(); /* try{ device.setResolution(100); // device.setRegionOfInterest(0.0,0.0,40.0,50.0); // top-left corner 40x50 mm device.setRegionOfInterest(0,0,400,500); // top-left corner 400x500 pixels device.setShowUserInterface(false); device.setShowProgressBar(false); }catch(Exception e){ e.printStackTrace(); } */ }else if(type.equals(ScannerIOMetadata.STATECHANGE)){ System.err.println(metadata.getStateStr()); }else if(type.equals(ScannerIOMetadata.EXCEPTION)){ metadata.getException().printStackTrace(); } }
完工
中间碰到最多的问题就是,applet签名及applet布署,开始老以为applet根平时的类布署是一样的,后来发现就应该把它当成一个独立的 应用来对等。
中间可能有错,有问题请在家指出,谢谢
效果图(点击中间的acquire就能扫描了):
![点击查看原始大小图片](http://hiphotos.baidu.com/sartre/pic/item/afc3793135557625ebc4af59.jpg)