1

我是网络 I/O 编程的新手,我遇到了一个障碍——基本上我想做的是让桌面应用程序与 google maps javascript API 对话。为了促进这一点,我构建了一个 java 小程序,它将充当桌面应用程序和浏览器 javascript 应用程序之间的桥梁。当我在 Eclipse 中一起运行桌面应用程序和小程序时,它们可以完美通信,并且我能够通过将字符串写入绑定到小程序已建立 ServerSocket 连接的同一端口的套接字来调用小程序函数。为了在 Eclipse 中进行测试,我将字符串“sendJSAlertTest”发送到套接字的输出流,然后使用 java.lang.reflect API 从 ServerSocket 输入流派生一个 Method 实例,最后在 applet 中调用生成的方法。当小程序在浏览器中运行时,我将“sendJSAlert”写入套接字,因为它会导致实际调用 javascript。在 Eclipse 中使用 appletviewer 的结果是桌面应用程序上下文打印输出“awesome sendJSAlert”,而applet 上下文打印来自 sendJSAlertTest() 方法的输出“Hello Client,我是服务器!”。将“sendJSAlert”传递给在浏览器中运行的小程序的结果是桌面应用程序打印 null,这表明由于某种原因 ServerSocket 的输入流为空,并且浏览器本身在它应该生成一个 JavaScript 警报框时什么也不做文本“你好客户端,我是服务器!”。我使用的浏览器是谷歌浏览器,

下面是相关的Java代码和HTML:

SocketClient.java

import java.awt.event.*;
import java.io.*;
import java.net.*;

public class SocketClient {


Socket socket = null;
PrintWriter out = null;
BufferedReader in = null;
private InetAddress myAddress;
private String remoteFunction;

public SocketClient(){

}


public void listenSocket(int portNum){
//Create socket connection

 try{
   System.out.println("@Client Trying to create socket bound to port " + portNum);
   socket = new Socket(<my hostname here as a string>, portNum);
   System.out.println("the attached socket port is " + socket.getLocalPort());
   System.out.flush();
   out = new PrintWriter(socket.getOutputStream(), true);
   out.println("sendJSAlertTest");
   in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
   String line = in.readLine();
   System.out.println("@CLient side Text received from server: " + line);
 } catch (UnknownHostException e) {
   System.out.println("Unknown host: <my hostname here as a string>.eng");
   System.exit(1);
 } catch  (IOException e) {
   System.out.println("No I/O");
   e.printStackTrace();
   System.exit(1);
 }
}

public void setRemoteFunction(String funcName){
  remoteFunction = funcName;
}
public String getRemoteFunction(){
 return remoteFunction;
}

}

SocketServer.java

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;

class SocketServer {


ServerSocket server = null;
Socket client = null;
BufferedReader in = null;
PrintWriter out = null;
String line;
private NetComm hNet;
private Method serverMethod;

SocketServer(NetComm netmain){
   hNet = netmain;



}



public void listenSocket(int portNum){

try{
  System.out.println("@server Trying to create socket bound to port " + portNum);
  server = new ServerSocket(portNum);
  System.out.println("the attached socket port is " + server.getLocalPort());
} catch (IOException e) {
  System.out.println("Could not listen on port " + portNum);
  System.exit(-1);
}

try{
  client = server.accept();
  System.out.println("Connection accepted!");
} catch (IOException e) {
  System.out.println("Accept failed: " + portNum);
  System.exit(-1);
}

try{
  in = new BufferedReader(new InputStreamReader(client.getInputStream()));
  out = new PrintWriter(client.getOutputStream(), true);
} catch (IOException e) {
  System.out.println("Accept failed: " + portNum);
  System.exit(-1);
}

while(true){
  try{
     System.out.println("trying to read from inputstream...");
    line = in.readLine();
    System.out.println(line);
    //Now that we have a method name, invoke it
    try {
        serverMethod = hNet.getClass().getDeclaredMethod(line,
  String.class);
    } catch (SecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    try {
        serverMethod.invoke(hNet, "Hello Client, I'm a Server!");
    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    //Send data back to client
    out.println("awesome " + line);


   } catch (IOException e) {
     System.out.println("Read failed");
     System.out.flush();
     System.exit(-1);
   }

  }
  }

  protected void finalize(){
  //Clean up
  try{
    in.close();
    out.close();
    server.close();
  } catch (IOException e) {
    System.out.println("Could not close.");
    System.exit(-1);
 }
}

public int getBoundLocalPort(){
  return server.getLocalPort();
}

}

NetComm.java

import cresco.ai.att.ccm.core.CCMMain;
import cresco.ai.att.ccm.gui.DataPanel;

import java.io.*;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;

import javax.servlet.*;
import javax.servlet.http.*;

import java.applet.*;


public class NetComm extends JApplet{//HttpServlet{
private CCMMain hMain;
private DataPanel dpLocal;
private SocketServer sockserver;
private Method serverMethod;

String testStr;
Integer testInt;  /*integer */
Character testChar; /*character*/

//Testing this...
ServerSocket server = null;
Socket client = null;
BufferedReader in = null;
PrintWriter out = null;
String line;






@Override
public void init(){
    sockserver = new SocketServer(this);


    //For offline debug (should be disabled in a release to the webapp):
            //initSocketServer is commented out in the release version and
            //invoked in the Eclipse testbed version.  In the webapp,
            //initSocketServer is invoked from javascript (see below js sockPuppet())
    //////initSocketServer(0);




    String msg = "Hello from Java (using javascript alert)";
    try {
        getAppletContext().showDocument(new URL("javascript:doAlert(\"" +
   msg +"\")"));
    }
    catch (MalformedURLException me) { }


}

public void sendJSAlertTest(String message){
    System.out.println("sendJSAlert remotely invoked, with message: " +
    message);

}

public void sendJSAlert(String message){

    try {
        getAppletContext().showDocument(new URL("javascript:doAlert(\"" +
    message +"\")"));
    }
    catch (MalformedURLException me) { }

}



public void initSocketServer(int portNum){
    sockserver.listenSocket(portNum);
}
public void finalizeSocketServer(){
    sockserver.finalize();
}

public int socket2Me(int portNum){

    try {
        socks.add(new ServerSocket(portNum));
        return 0; //socket opened successfully
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        return -1; //socket failed to open
    }




}

public int getSocketServerPort(){
    return sockserver.getBoundLocalPort();
}


public void showRectTest(){
    try {
        getAppletContext().showDocument(new
                    URL("javascript:overlayRect()"));
    }
    catch (MalformedURLException me) { }
}




public void setGUI(DataPanel d){
    dpLocal = d;
}


 }

MapViz.html

<html>
<head>
<title>Welcome to Geographic Midpoint Map Vizualization!</title>
<meta name="viewport"
    content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta charset="UTF-8">
<link href="https://google-developers.appspot.com/maps/documentation/javascript
/examples/default.css"
    rel="stylesheet" type="text/css">

...google maps stuff omitted...

<script type="text/javascript">
<script type="text/javascript">
function overlayRect(){
    var c=document.getElementById("myCanvas");
    var ctx=c.getContext("2d");
    ctx.fillStyle="#FF0000";
    ctx.fillRect(0,0,150,75);
}
function doAlert(s){
    alert(s);
}
function testJava(){
    document.ccmApplet.showRectTest();
}
function sockPuppet(){
    var i = parseInt(document.getElementById("args").value,10);
    alert("parsing the input args... got " + i);
    if(i == NaN || i == null){
        i = 0;
    }
    alert("passed NaN OR null block, i is " + i);
    //i = 6672; //because $%*& you, that's why!
    document.ccmApplet.initSocketServer(i);
    //document.ccmApplet.listenSocket(i);
    alert("inittializing socket server...");
    //queryPort();
    alert("querying port...");
    document.ccmApplet.finalizeSocketServer();
    //document.ccmApplet.finalize();
    alert("finalizing socket server...");
}
function queryPort(){
    var d = document.getElementById("debug");
    var s1 = "Last port opened was: ";
    //var s2 = document.ccmApplet.getLastBoundPort();
    var s2 = document.ccmApplet.getSocketServerPort();
    var sFinal = s1.concat(s2);
    d.value = sFinal;
}
</script>
 </head>
 <body>
 <applet width="500" height="50" name="ccmApplet" archive="CCM.jar"
 code="cresco.ai.att.ccm.io.NetComm" MAYSCRIPT></applet>
 <p></p>
<canvas id="myCanvas" width="200" height="100"></canvas>
 <div id="map_canvas"></div>
<input id="args" type="textentry" value="" />
<button height="50" width="50" onClick="sockPuppet()">Test Socket
    Creation</button>
<input id="debug" type="debugthingy" value="debug area..." />
<button height="50" width="50" onClick="testJava()">Test Java Callback</button>
  </body>
 </html>

在 webapp 中,我用本地机器上的有效端口号填写 args 输入,然后按下调用 sockPuppet() javascript 的 Test Socket Connection 按钮。这应该创建一个 ServerSocket 并将其绑定到指定的端口,然后我通过 SocketClient.listenSocket 将我的桌面客户端应用程序单独连接到该端口。Eclipse 在桌面应用程序上下文中的结果是“很棒的 sendJSAlertTest”,而在 appletviewer 上下文中是输出“远程调用 sendJSAlert,带有消息:Hello Client,我是服务器!”。调用 sendJSAlert() 的 web 应用程序应该在同一条消息上调用 javascript 警报函数,创建一个弹出框,其中包含消息“sendJSAlert 远程调用,消息:Hello Client,我是服务器!”

所以问题是:不同结果的原因可能是什么?我知道浏览器的安全沙箱可能是一个问题,但我已经包含了一个权限文件,它应该允许通过任何 localhost 端口上的套接字进行通信:

grant {
 permission java.net.SocketPermission
    "localhost:1024-", 
 "accept, connect, listen, resolve";
};

虽然我没有正确应用权限,但肯定有可能(我使用了 sun policytool gui);在小程序代码(如果有的话)中究竟需要做什么来应用权限?安全问题会导致我看到没有响应吗?我希望在 Chrome 的 java 调试控制台中报告一个异常,但没有任何...

任何帮助将不胜感激,谢谢!

-CCJ

更新:好的,一些新信息:我在打开 javascript 控制台的情况下再次在 Chrome 中运行小程序(可以发誓我之前尝试过但没有效果,但显然没有)并收到以下控制台输出 -

"Uncaught Error: java.security.AccessControlException: access denied 
("java.net.SocketPermission" "<myipaddress>:4218" "accept,resolve") MapVizApp.html:154
 sockPuppet MapVizApp.html:154 onclick MapVizApp.html:179 Uncaught Error: Error 
 calling method on NPObject. sockPuppet onclick " 

所以现在的问题是我为什么要触发这个安全异常?上面给定权限的策略文件与html页面和包含applet的jar文件在同一个工作目录下,我在我系统的JRE安全策略文件中添加了以下内容

//Grants my NetComm applet the ability to accept, connect, and listen on unpriv. ports
grant codeBase "file:${user.home}\Desktop\dev\invention\ATT\MapViz\CCM.jar" {
  permission java.net.SocketPermission
    "localhost:1024-", 
  "accept, connect, listen, resolve";
};

我还没有签署小程序,但我的理解是,如果策略文件是有序的,则不需要签署小程序......如果我错了,请告诉我。无论如何,尽管策略文件具有上述授予权限,但是否有人对为什么抛出此安全异常有任何建议?JRE 查找的工作目录中的策略文件是否有命名约定?我现在的工作目录策略文件只是命名为 ccmPolFile,但我不清楚 JRE 应该如何定位它;是否需要在小程序代码中添加一些内容以将 JRE 指向预期的工作目录策略文件?此外,我添加的系统策略文件授权本身是否足以满足我在 CCM.jar 中的小程序的套接字权限?

更新 2:我签署了 applet 并将行 policy.url.3=file:${user.home}\Desktop\dev\invention\ATT\MapViz\ccmPolFile.policy 添加到我的 java.security 文件中 ${java. home}/lib/security (通过http://docs.oracle.com/javase/tutorial/security/tour2/step4.html#Approach2这显然是 JRE 定位要加载的策略文件的方式)......遗憾的是,结果是完全相同的安全异常。我所知道的唯一剩下的就是

AccessController.doPrivileged(new PrivilegedAction() {
    public Object run() {
        // perform the security-sensitive operation here
        return null;
    }
});

自从小程序现在签名以来,这应该让我几乎可以做任何事情。我想继续退出等式,但策略文件由于某种原因无法正常工作。我很快就会回来看看结果如何

4

1 回答 1

1

对,所以在我上面的更新 2 之后,我将 SocketServer.java 代码中的 listenSocket() 方法更改为

public void listenSocket(int portNum){
  AccessController.doPrivileged(new PrivilegedAction() {
        public Object run() {
            int portNum = 4444;
try{
  System.out.println("@server Trying to create socket bound to port " + portNum);
  server = new ServerSocket(portNum); 
  System.out.println("the attached socket port is " + server.getLocalPort());
} catch (IOException e) {
  System.out.println("Could not listen on port " + portNum);
  System.exit(-1);
}

try{
  client = server.accept();
  System.out.println("Connection accepted!");
} catch (IOException e) {
  System.out.println("Accept failed: " + portNum);
  System.exit(-1);
}

try{
  in = new BufferedReader(new InputStreamReader(client.getInputStream()));
  out = new PrintWriter(client.getOutputStream(), true);
} catch (IOException e) {
  System.out.println("Accept failed: " + portNum);
  System.exit(-1);
}

while(portNum==4444){
  try{
     System.out.println("trying to read from inputstream...");
    line = in.readLine();
    System.out.println(line);
    //Now that we have a method name, invoke it
    try {
        serverMethod = hNet.getClass().getDeclaredMethod(line, 
     String.class);
    } catch (SecurityException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    try {
        serverMethod.invoke(hNet, "Hello from Javascript invoked by the
    desktop app!");
    } catch (IllegalArgumentException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    //Send data back to client
    out.println("awesome " + line);
    //System.out.println(line);
    //System.out.flush();

  } catch (IOException e) {
    System.out.println("Read failed");
    System.out.flush();
    System.exit(-1);
  }

}

return null;
        }
    });//end doPrivileged
}

显然这是一个不安全的组合,但它可以解决问题——我没有收到任何安全异常,桌面应用程序会打印“awesome sendJSAlert”,所以我知道 IO 正在通过套接字在客户端和服务器上下文之间工作。实际的 js 警报功能没有触发,但我认为这与上面 listenSocket() 中可怕的无限 while 循环有关......

带回家的消息:出于某种原因,为了从谷歌浏览器中的小程序建立套接字连接,我需要签署小程序并使用 AccessController.doPrivileged() 来调用我的安全敏感代码,尽管我已经设置了我的本地策略和安全文件来授予我的权限小程序这些权限

谷歌人看到参考:

http://www.coderanch.com/how-to/java/HowCanAnAppletReadFilesOnTheLocalFileSystem

http://docs.oracle.com/javase/7/docs/api/java/security/AccessController.html

http://www-personal.umich.edu/~lsiden/tutorials/signed-applet/signed-applet.html

http://docs.oracle.com/javase/tutorial/security/tour2/step4.html

更新:终于 100% 工作:DI 将上面 SocketServer.java 中的 listenSocket() 方法更改为:

public void listenSocket(int portNum){
  AccessController.doPrivileged(new PrivilegedAction() {
        public Object run() {
            int portNum = 4444;
try{
  System.out.println("@server Trying to create socket bound to port " + portNum);
  server = new ServerSocket(portNum); 
  System.out.println("the attached socket port is " + server.getLocalPort());
} catch (IOException e) {
  System.out.println("Could not listen on port " + portNum);
  System.exit(-1);
}

try{
  client = server.accept();
  System.out.println("Connection accepted!");
} catch (IOException e) {
  System.out.println("Accept failed: " + portNum);
  System.exit(-1);
}

try{
  in = new BufferedReader(new InputStreamReader(client.getInputStream()));
  out = new PrintWriter(client.getOutputStream(), true);
} catch (IOException e) {
  System.out.println("Accept failed: " + portNum);
  System.exit(-1);
}

try {
    line = in.readLine();
    System.out.println("line is " + line + " from the inputstream to the 
            serversocket");
} catch (IOException e1) {
    // TODO Auto-generated catch block
    e1.printStackTrace();
}

if(line != null){
  System.out.println("trying to read from non-null inputstream...");
//line = in.readLine();
System.out.println(line);
//Now that we have a method name, invoke that bitch!
try {
    serverMethod = hNet.getClass().getDeclaredMethod(line, String.class);
} catch (SecurityException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (NoSuchMethodException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

try {
    serverMethod.invoke(hNet, "Hello From Javascript invoked by a desktop 
             app!");
} catch (IllegalArgumentException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalAccessException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (InvocationTargetException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

//Send data back to client
out.println("awesome " + line);
//System.out.println(line);
//System.out.flush();

 }

return null;
        }
    });//end doPrivileged
}

server.accept() 方法会一直阻塞,直到建立连接为止,因此对于我只想一次将一个命令传递给 serversocket 输入流的这种情况,while 循环没有意义。对 if 的更改允许程序实际继续执行 java.reflect 内容,该内容调用 applet 中的一个方法,该方法直接调用 javascript 函数。由于端口仍然是硬编码的并且小程序使用 doPrivileged(...) 这仍然不是一个很好的解决方案,但它确实满足了通过 Java 小程序桥从桌面 Java 应用程序调用 Web 浏览器中的 JavaScript 的用例所以它为更强大的实现提供了一个很好的跳板!

于 2012-07-19T21:08:07.177 回答