java 编辑器 集成jupyter notebook

百度搜出来的都是什么辣鸡方案,嵌入个页面你TM叫集成?好意思么!好在努力的人都比较幸运,在一个python大神的帮助下实现了集成方案。废话不多少,直接上方案。本文不会贴太多代码,只描述关键点,有问题可以私信交流,别找我要源码,思路提供了,自力更生才能进步。

既然是集成jupyter,那么最简单的办法就是自己请求jupyter sever接口。jupyter本身就是一个前后端项目,其实集成jupyter核心逻辑就是用自己的前端页面请求jupyter server。jupyter server api 可以从The REST API — Jupyter Server documentation 获取,也可以自己启动一个jupyter项目,通过浏览器查看页面请求获取。这个方案是可行,只是获取token 比较麻烦,而且会话还要基于文档创建,感觉比较费事,有兴趣的自行研究吧。我采用的是另外一种方案,就是jupyter kernelgateway ,这个也有文档,不过需要翻墙。其实这个感觉就是启动一个纯净的jupyter server 服务,没有乱七八糟的权限控制,建议用这个。启动命令很简单:

jupyter kernelgateway --JupyterWebsocketPersonality.list_kernels=True

那么如何交互进行命令发送和结果获取呢,继续往下看。因为项目原因, 我采用的是三方通信模式,就是前端命令先发送到java服务端,然后java服务端再转发给jupyter server。

编辑器我用的是微软的monaco编辑器,其它编辑器也行,如果后面要做python动态代码提示的话建议用微软的monaco编辑器,毕竟lsp 语言服务协议都是人家定,monaco有天生优势。

编辑器初始化也很简单,我简单贴下我的代码,网上一大堆编辑器初始化方案。

var el =document.getElementById("scriptTt"+id) ;
			// create Monaco editor
			var scriptCodeMirror= monaco.editor.create(document.getElementById("scriptTt"+id), {
 					model: monaco.editor.createModel(nowScript, "python"),
					glyphMargin: true,
					lightbulb: {
						enabled: true
					},
					scrollBeyondLastLine: false,
					automaticLayout: true,
 					autoIndent:true,//自动布局
					 minimap: { // 关闭代码缩略图
						enabled: true // 是否启用预览图
					},
 					fontSize: 10,
					wordWrap: "bounded",
 					wrappingIndent: 'indent'
				});

然后就是和java服务端建立websocket 连接。我用的是xterm.js +webssh.js ,代码贴出来参考一下,用什么都行,只要能建立websocket就行。

//初始化客户端
client = new WSSHClient('${applicationScope["webSshServer"]}');
 //建立websocket 连接
		openJupyterTerminal({
			operate : 'connect',
			jupyterKerneUrl:$("#kernelUrl").val()
		});



function openJupyterTerminal(options) {

		//执行连接操作
		client.connect({
			onError : function(error) {
				//连接失败回调
				term.write('Error: ' + error + '\r\n');
			},
			onConnect : function() {
				//连接成功回调
 				  client.sendInitData(options);
			},
			onClose : function() {
				//连接关闭回调
				$.messager.alert('信息提示', '连接已关闭!', 'info');

			},
			onData : function(data) { 
				console.log("curtRunner-----------------");
				console.log(curtRunner);
				console.log(data);
			   if(curtRunner=="python"){
				   data=$.parseJSON( data ); 
                   if(data.status==0){
					  runflag=true;
					  $("#jupyterConnect").css("display","none");
					  $("#jupyterOffConnect").css("display","inline");
					$.messager.alert("提示", "交互式环境加载完成!", "info");
				  };
				  if(data.status==1){
					msgData=$.parseJSON( data.msgData ); 
					console.log(msgData);
					if(msgData.msg_type=="execute_result"){
							if(selectLogCodeMirror != null){
								selectLogCodeMirror.setValue(selectLogCodeMirror.getValue()+msgData.content.data["text/plain"]);	 
							}
					};
					if(msgData.msg_type=="display_data"){
						if(msgData.content.data["image/png"]){
							$("#figureImg").attr("src", "data:image/jpg;base64,"+msgData.content.data["image/png"]);
							$('#wz').window('open');
						}	   
					}
					if(msgData.msg_type=="stream"){
						   if(selectLogCodeMirror != null){
							   selectLogCodeMirror.setValue(selectLogCodeMirror.getValue()+msgData.content.text);
 							}
					};
					if(msgData.msg_type=="status"){
						 if(msgData.content.execution_state=="idle"){
							 pythonRuning=false;
							// $($(runCellId).find(".runCell")).css("display","inline"); 
							// $($(runCellId).find(".stopCell")).css("display","none");
							$($(runCellId).find(".outSpan")).html("执行结果");
						 }else{
							// $($(runCellId).find(".runCell")).css("display","none"); 
							// $($(runCellId).find(".stopCell")).css("display","inline");
							 pythonRuning=true;
						 };

					};

					if(msgData.msg_type=="error"){
						 $.messager.alert("提示", "代码执行异常。<br/>"+msgData.content.evalue , "error");
					};
					
					if(msgData.msg_type=="shutdown_reply"){
						if(msgData.content.status="ok"){
							 $("#jupyterConnect").css("display","inline");
					 		 $("#jupyterOffConnect").css("display","none");
							$.messager.alert("提示", "jupyter server 连接已断开。<br/>", "error");
						}
						
					}
  					 
				  };
				  if(data.status==2||data.status==3){
					runflag=false;
					 $("#jupyterConnect").css("display","inline");
					 $("#jupyterOffConnect").css("display","none");
					msgData=data.msgData; 
  					$.messager.alert("提示", "连接异常,请检查jupyer服务。<br/>"+msgData,"error" );
				  }
			   }
			}
		});
	}

这个代码无非就是建立连接,然后解析接收到的数据,没什么好说的。

然后来说java服务端。java服务端的职责就是接收前端命令,然后转发给jupyter。初次接收命令会进行初始化操作,初始化的时候会连接jupyter server。关键代码如下:

private void connectToJupyerServer(JupyterClientInfo jupyterClientInfo, JupyterClientData jupyterClientData, WebSocketSession webSocketSession) throws Exception {
        logger.info("connectToJupyerServer");
        String  baseUrl="";
        String[]  baseUrlArr=jupyterClientData.getJupyterKerneUrl().split("://");
        if(baseUrlArr.length==2){
            baseUrl=baseUrlArr[1];
        }else{
            baseUrl= baseUrlArr[0];
        };
        jupyterClientInfo.setJupyerUrl(baseUrl);

        String result=HttpUtils.httpRequestToString("http://"+baseUrl+"/api/kernels","GET",null);
        System.out.println("-----kernels------------");
        System.out.println(result);
        JSONArray kernelsJson= JSONArray.parseArray(result);
        JSONObject kernelobj=new JSONObject();
        if(kernelsJson.size()>0){
            kernelobj=(JSONObject) kernelsJson.get(0);
        }else{
            //获取 kernel
            result = HttpUtils.httpRequestToString("http://"+baseUrl+"/api/kernelspecs","GET",null);
            System.out.println("-----kernelspecs------------");
            System.out.println(result);
            JSONObject kernelspecs= JSONObject.parseObject(result);
            String kernelsNmae=kernelspecs.getString("default");
            JSONObject newkernelsJson=new JSONObject();
            newkernelsJson.put("name", kernelsNmae);
            newkernelsJson.put("path", "/opendata");
            //创建内核
            result=HttpUtils.httpRequestToString("http://"+baseUrl+"/api/kernels","POST", JSONObject.toJSONString(newkernelsJson) );
            kernelobj=JSONObject.parseObject(result);
        };

        if (kernelobj!=null){
            System.out.println("server url---------");
            System.out.println("ws://"+baseUrl+String.format("/api/kernels/%s/channels",kernelobj.get("id")));
            jupyterClientInfo.setKernelId(kernelobj.get("id").toString());


            WebSocketClient webSocketClient = new WebSocketClient(new URI("ws://"+baseUrl+String.format("/api/kernels/%s/channels",kernelobj.get("id"))),new Draft_6455()) {
                @SneakyThrows
                @Override
                public void onOpen(ServerHandshake serverHandshake) {
                    logger.info("[连接 jupyter server] 连接成功");
                    SocketMsgData socketMsgData=new SocketMsgData();
                    socketMsgData.setStatus(SocketMsgData.StatusEnum.ONOPEN.ordinal());
                    socketMsgData.setMsgData("[连接 jupyter server] 连接成功");
                    if(webSocketSession.isOpen()){
                        sendMessage(webSocketSession, JSONObject.toJSONString(socketMsgData).getBytes(StandardCharsets.UTF_8));
                    }
                }

                @SneakyThrows
                @Override
                public void onMessage(String message) {
                    logger.info("[jupyter client] 收到消息={}",message);
                    SocketMsgData socketMsgData=new SocketMsgData();
                    socketMsgData.setStatus(SocketMsgData.StatusEnum.ONMESSAGE.ordinal());
                    socketMsgData.setMsgData(message);
                    if(webSocketSession.isOpen()){
                        sendMessage(webSocketSession, JSONObject.toJSONString(socketMsgData).getBytes(StandardCharsets.UTF_8));
                    }
                }

                @SneakyThrows
                @Override
                public void onClose(int code, String reason, boolean remote) {
                    logger.info("[jupyter client] 退出连接");
                    SocketMsgData socketMsgData=new SocketMsgData();
                    socketMsgData.setStatus(SocketMsgData.StatusEnum.ONCLOSE.ordinal());
                    socketMsgData.setMsgData("[jupyter client] 退出连接");
                    if(webSocketSession.isOpen()){
                        sendMessage(webSocketSession, JSONObject.toJSONString(socketMsgData).getBytes(StandardCharsets.UTF_8));
                    }
                }

                @SneakyThrows
                @Override
                public void onError(Exception ex) {
                    logger.info("[jupyter client] 连接错误={}",ex.getMessage());
                    SocketMsgData socketMsgData=new SocketMsgData();
                    socketMsgData.setStatus(SocketMsgData.StatusEnum.ONCLOSE.ordinal());
                    socketMsgData.setMsgData("[jupyter client] 连接错误={}"+ex.getMessage());
                    if(webSocketSession.isOpen()){
                        sendMessage(webSocketSession, JSONObject.toJSONString(socketMsgData).getBytes(StandardCharsets.UTF_8));
                    }
                }
            };
            webSocketClient.connect();
            jupyterClientInfo.setWebSocketClient(webSocketClient);
        }else{
            throw new Exception("找不到kernel信息,请确认jupyter kernelgateway服务是否启动");
        }



    }

说一下java服务端大概逻辑,由于服务端是用来做转发的,所以它是websocket服务端也是websocket的客户端。初始化的时候,会把服务端session 和客户端实例保存到全局map对象里面,用的时候就从全局对象取出来就行。

package cn.objectspace.jupyter.form;

import com.alibaba.fastjson.JSONObject;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import lombok.Data;
import org.java_websocket.client.WebSocketClient;
import org.springframework.web.socket.WebSocketSession;

import java.util.UUID;

/**
* @Description:  jupyter 连接信息
* @Author: zxf
* @Date: 2021/3/23
*/
@Data
public class JupyterClientInfo {
    public JupyterClientInfo(){
        //组装ws 信息
        JSONObject contentJson=new JSONObject();
        contentJson.put("code", "1==1");
        contentJson.put("silent", false);
        contentJson.put("store_history", false);
        contentJson.put("user_expressions", new JSONObject());
        contentJson.put("allow_stdin", false);
        JSONObject hdrJson=new JSONObject();
        hdrJson.put("msg_id", UUID.randomUUID().toString().replaceAll("-",""));
        hdrJson.put("username", "dataOpen");
        hdrJson.put("session", UUID.randomUUID().toString().replaceAll("-",""));
        hdrJson.put("msg_type", "execute_request");
        hdrJson.put("version","5.0");
        JSONObject msgJson=new JSONObject();
        msgJson.put("header", hdrJson);
        msgJson.put("metadata", new JSONObject());
        msgJson.put("parent_header", new JSONObject());
        msgJson.put("buffers", new JSONObject());
        msgJson.put("content", contentJson);
        msgJson.put("channel","shell");
        this.jupyerConent=contentJson;
        this.jupyerMsg=msgJson;
    }
    //客户端连接
    private WebSocketSession webSocketSession;
    // 转发jupytersever 的客户端
    private WebSocketClient  webSocketClient;
    //jupyer 消息体
    private JSONObject jupyerMsg;
    //jupyer 消息内容
    private JSONObject jupyerConent;

    //jupyter url
    private String jupyerUrl;

    //jupyter url
    private String kernelId;
}

转发给jupyter的时候,需要消息组装。

   private void transToJupyerServer(JupyterClientInfo jupyterClientInfo, String command) throws IOException {
       WebSocketClient webSocketClient=jupyterClientInfo.getWebSocketClient();
        if (webSocketClient != null) {
            JSONObject msg=jupyterClientInfo.getJupyerMsg();
            JSONObject  content=jupyterClientInfo.getJupyerConent();
            content.put("code",command);
            msg.put("content",content);
            JSONObject  header=msg.getJSONObject("header");
            header.put("msg_id",UUID.randomUUID().toString().replaceAll("-",""));
            header.put("date",getISO8601Timestamp(new Date()));
            msg.put("header",header);
            logger.info("[消息发送] "+msg);
            webSocketClient.send(JSONObject.toJSONString(msg));
        }
    }

再说一下注意事项,jupyter创建的内核断开连接的时候要注意主动回收,不回收的话python进程会一直存在。如果是多用户访问,那么每个用户都应该去单独创建维护内核 。这也是为什么采用三方通信的初衷,js去维护实在不靠谱!

整体方案大概就这样,如果有什么不明白的,留言吧。

最后展示一下效果。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
### 回答1: 在JupyterLab中,可以使用IJava内核来实现Java代码补全。首先,确保已经安装了IJava内核,可以通过以下命令安装: ``` !conda install -c conda-forge java !conda install -c conda-forge/label/cf202003 java !conda install -c conda-forge/label/cf201901 java !conda install -c conda-forge/label/cf201812 java !conda install -c conda-forge/label/cf201807 java !conda install -c conda-forge/label/cf java !conda install -c conda-forge/label/broken java !conda install -c conda-forge/label/borken java !conda install -c conda-forge/label/rc java !conda install -c conda-forge/label/dev java ``` 然后,在JupyterLab中创建一个新的Notebook,并选择IJava内核。接下来,可以编写Java代码,并使用Tab键触发代码补全。代码补全功能将显示可用的Java类、方法和变量等。 请注意,IJava内核需要Java运行时环境(JRE)或Java开发工具包(JDK)。如果你尚未安装Java,请先安装它。 ### 回答2: JupyterLab是一种基于Web的交互式开发环境,它支持多种编程语言,包括Python、R、Julia等。然而,对于Java代码来说,在JupyterLab中并没有原生的补全功能。这是由于Java的特殊性:Java是一种编译型语言,需要经过编译器的处理才能获得补全信息。 但是,我们可以通过一些其他步骤来实现在JupyterLab中补全Java代码的功能。下面是一种可能的解决方案: 1. 首先,你需要安装并配置Java语言服务器。Java语言服务器是一种能够提供Java代码补全和其他功能的工具。目前比较常用的Java语言服务器有Eclipse JDT Language Server和Microsoft Java Language Server。 2. 其次,在JupyterLab中安装并启动Java语言服务器的插件。你可以在JupyterLab的扩展商店中搜索并安装相应的插件。 3. 完成插件安装后,你可以打开一个新的JupyterLab文件,选择Java语言作为代码类型。在编辑器中输入Java代码,通过按下Tab键或其他快捷键,就可以触发代码补全功能。 需要注意的是,这种方法仍然有一些局限性。因为Java语言的特性,对于某些复杂的代码结构,补全功能可能存在一定的不准确性。而且Java语言服务器的性能也会对补全功能的准确性和速度有一定影响。 总的来说,虽然JupyterLab本身不提供原生的Java代码补全功能,但通过使用Java语言服务器插件,我们可以在JupyterLab中实现类似的功能。这样可以提高我们在JupyterLab中开发和测试Java代码的效率和便捷性。 ### 回答3: JupyterLab是一种基于Web的交互式开发环境,用于进行各种编程任务。它提供了对不同编程语言的支持,包括Python、R、Julia和Java等。 然而,JupyterLab的默认设置并不提供对Java代码的完全支持。这是因为Java是一种静态类型语言,需要进行编译才能执行。与动态类型语言(如Python)相比,Java代码的自动补全要复杂得多。 然而,如果你希望在JupyterLab中使用Java代码并获得代码补全的功能,有一些方法可以尝试: 1. 使用Java内核:JupyterLab可以使用不同的内核来执行不同的编程语言。虽然Java内核不是JupyterLab的默认内核,但可以通过安装合适的Java内核来为Java代码提供支持。例如,使用IJava内核可以在JupyterLab中编写和执行Java代码,并获得部分代码补全功能。 2. 使用插件:JupyterLab支持使用插件来增强其功能。可能有一些插件能够提供对Java代码的更全面的自动补全支持。你可以在JupyterLab的插件存储库中搜索这些插件并进行尝试。 3. 结合其他工具:如果你希望在JupyterLab中使用Java代码并获得完全的代码补全功能,你可以考虑结合其他集成开发环境(IDE)或编辑器使用。例如,你可以在JupyterLab中使用代码单元格编写Java代码,并在其他IDE中打开相同的Java项目,并从IDE中复制粘贴代码以获得更完整的补全功能。 需要注意的是,Java的静态类型特性使得完全的代码补全在JupyterLab中有限。如果你需要Java代码的完全补全功能,推荐使用专门为Java开发设计的IDE,如Eclipse或IntelliJ IDEA。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值