AppRunner本地运行

本文档详细介绍了如何在本地环境中配置和运行AppRunner。首先确保安装了Java 1.8+、Git和Maven,并设置了M2_HOME环境变量。接着从GitHub克隆AppRunner源代码,使用IDE打开并执行mvn clean install。成功后运行RunLocal的main方法,日志显示成功启动表明运行成功。注意,如果遇到GitHub连接问题,可能需要解决网络稳定性问题。

目录

前言

环境准备

下载源代码

编译和运行

参考


前言

本文介绍如何在本地环境编译、运行AppRunner。

环境准备

Java 1.8+

D:\>java -version
java version "11.0.9" 2020-10-20 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.9+7-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.9+7-LTS, mixed mode)

git

D:\>git version
git version 2.27.0.windows.1

maven

D:\>mvn -v
D:\
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: D:\sandbox\apache-maven-3.6.3\bin\..
Java version: 11.0.9, vendor: Oracle Corporation, runtime: D:\sandbox\Java\jdk-11.0.9
Default locale: zh_CN, platform encoding: GBK
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"

设置M2_HOME环境变量

D:\>echo %M2_HOME%
D:\sandbox\apache-maven-3.6.3

网络:由于国内上github不稳定,建议自备科学上网 

下载源代码

代码已开源 https://github.com/danielflower/app-runner

git clone https://github.com/danielflower/app-runner.git

编译和运行

1. 用IntelliJ(或其他IDE)打开工程

2. 进行mvn clean install,所有unit test都应该通过且maven build成功

 3. 运行RunLocal里的main方法

4. 看到如下日志表示运行成功

00:23:43,935 [main] INFO  Config - Using config file: D:\sandbox\code\app-runner\sample-config.properties
00:23:44,009 [main] INFO  App - SystemInfo{hostName='LAPTOP-ODF35RAF', user='bin9w', pid=14992, publicKeys=[], osName='Windows 10', numCpus=8}
00:23:44,016 [main] INFO  App - Deleting contents of temporary folder at D:\sandbox\code\app-runner\target\local\temp
00:23:44,022 [main] INFO  App - File deletion complete in 7ms
00:23:44,024 [main] INFO  App - Detecting providers...
00:23:45,260 [main] INFO  App - Registered providers...
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) - Maven home: D:\sandbox\apache-maven-3.6.3\bin\.. - Java version: 11.0.9, vendor: Oracle Corporation, runtime: D:\sandbox\Java\jdk-11.0.9 - Default locale: zh_CN, platform encoding: GBK - OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
Node v12.18.3 with NPM 6.14.6
00:23:45,262 [main] INFO  AppEstate - Loading app [app-runner-home] (git:https://github.com/danielflower/app-runner-home.git)
00:23:45,515 [main] INFO  AppManager - Clone app app-runner-home from https://github.com/danielflower/app-runner-home.git to D:\sandbox\code\app-runner\target\local\repos\app-runner-home
00:24:25,602 [main] INFO  AppManager - Created app manager for app-runner-home in dir D:\sandbox\code\app-runner\target\local\apps\app-runner-home\data
00:24:26,274 [main] INFO  WebServer - Started web server at null / http://localhost:8080
00:24:27,098 [pool-5-thread-1] INFO  AppManager - getting the contributors [dependabot[bot], Daniel Flower, Stephen Benson, Rajesh Shah, Rika]
00:24:27,125 [pool-5-thread-1] INFO  AppManager - Using maven for app-runner-home
00:24:27,128 [pool-5-thread-1] INFO  MavenRunner - Building maven project at D:\sandbox\code\app-runner\target\local\temp\app-runner-home\instances\1636993467099
00:24:34,122 [pool-5-thread-1] INFO  MavenRunner - Build successful. Going to start app.
00:24:34,139 [pool-5-thread-1] INFO  ProcessStarter - Starting D:\sandbox\code\app-runner\target\local\temp\app-runner-home\instances\1636993467099> D:\sandbox\Java\jdk-11.0.9\bin\java.exe -Djava.io.tmpdir=D:\sandbox\code\app-runner\target\local\temp\app-runner-home -jar target\app-runner-home-1.0-SNAPSHOT.jar
00:24:35,309 [pool-5-thread-1] INFO  ProcessStarter - Completed D:\sandbox\Java\jdk-11.0.9\bin\java.exe in 1170ms
00:24:35,320 [pool-5-thread-1] INFO  ProxyMap - app-runner-home maps to http://localhost:50019/app-runner-home

这时在浏览器打开http://localhost:8080/app-runner-home/就可以看到AppRunner主页了,证明本地运行成功。

5. 我曾经看到过如下错误日志,是连接github不稳定导致的,重跑几次解决了。

00:19:35,774 [main] INFO  AppEstate - Loading app [app-runner-home] (git:https://github.com/danielflower/app-runner-home.git)
00:19:36,019 [main] INFO  AppManager - Clone app app-runner-home from https://github.com/danielflower/app-runner-home.git to D:\sandbox\code\app-runner\target\local\repos\app-runner-home
00:20:16,222 [main] WARN  App - Error while trying to initiliase app-runner-home (https://github.com/danielflower/app-runner-home.git) - will ignore this app.
org.eclipse.jgit.api.errors.TransportException: https://github.com/danielflower/app-runner-home.git: cannot open git-upload-pack
	at org.eclipse.jgit.api.FetchCommand.call(FetchCommand.java:222)
	at org.eclipse.jgit.api.CloneCommand.fetch(CloneCommand.java:302)
	at org.eclipse.jgit.api.CloneCommand.call(CloneCommand.java:178)
	at com.danielflower.apprunner.mgmt.AppManager.create(AppManager.java:71)
	at com.danielflower.apprunner.AppEstate.addApp(AppEstate.java:63)
	at com.danielflower.apprunner.App.start(App.java:72)
	at com.danielflower.apprunner.App.main(App.java:214)
	at com.danielflower.apprunner.RunLocal.main(RunLocal.java:10)
Caused by: org.eclipse.jgit.errors.TransportException: https://github.com/danielflower/app-runner-home.git: cannot open git-upload-pack
	at org.eclipse.jgit.transport.TransportHttp.connect(TransportHttp.java:605)
	at org.eclipse.jgit.transport.TransportHttp.openFetch(TransportHttp.java:362)
	at org.eclipse.jgit.transport.FetchProcess.executeImp(FetchProcess.java:105)
	at org.eclipse.jgit.transport.FetchProcess.execute(FetchProcess.java:91)
	at org.eclipse.jgit.transport.Transport.fetch(Transport.java:1260)
	at org.eclipse.jgit.api.FetchCommand.call(FetchCommand.java:211)
	... 7 common frames omitted
Caused by: javax.net.ssl.SSLException: Connection reset
	at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:127)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:353)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:296)
	at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:291)
	at java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1522)
	at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:965)
	at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:252)
	at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:292)
	at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:351)
	at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:746)
	at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:689)
	at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:717)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1615)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1520)
	at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:527)
	at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:334)
	at org.eclipse.jgit.transport.http.JDKHttpConnection.getResponseCode(JDKHttpConnection.java:85)
	at org.eclipse.jgit.util.HttpSupport.response(HttpSupport.java:208)
	at org.eclipse.jgit.transport.TransportHttp.connect(TransportHttp.java:510)
	... 12 common frames omitted
	Suppressed: java.net.SocketException: Connection reset by peer: socket write error
		at java.base/java.net.SocketOutputStream.socketWrite0(Native Method)
		at java.base/java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:110)
		at java.base/java.net.SocketOutputStream.write(SocketOutputStream.java:150)
		at java.base/sun.security.ssl.SSLSocketOutputRecord.encodeAlert(SSLSocketOutputRecord.java:83)
		at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:384)
		... 29 common frames omitted
Caused by: java.net.SocketException: Connection reset
	at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
	at java.base/java.net.SocketInputStream.read(SocketInputStream.java:140)
	at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:476)
	at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:470)
	at java.base/sun.security.ssl.SSLSocketInputRecord.bytesInCompletePacket(SSLSocketInputRecord.java:70)
	at java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1308)
	at java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:949)
	... 25 common frames omitted
00:20:17,062 [main] INFO  WebServer - Started web server at null / http://localhost:8080

参考

https://github.com/danielflower/app-runner

代码一:import sys import asyncio from PyQt5.QtWidgets import QApplication from qasync import QEventLoop from server.src.mvc.controller.server_controller import ServerController async def run_server(): controller = ServerController() await controller.start() # 正确等待异步函数 # print(f"Pool created in start: {id(asyncio.get_event_loop())}") if __name__ == '__main__': app = QApplication(sys.argv) loop = QEventLoop(app) asyncio.set_event_loop(loop) # print(f"Pool created in main: {id(asyncio.get_event_loop())}") with loop: loop.create_task(run_server()) # 创建异步任务 sys.exit(loop.run_forever()) # 启动 Qt 主循环 代码二:from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit, QTableWidget, QTableWidgetItem, QTextEdit, QStatusBar, QMessageBox, QHeaderView) from PyQt5.QtCore import Qt, QDateTime from PyQt5.QtGui import QFont import asyncio class ServerView(QMainWindow): """服务器管理界面""" def __init__(self, controller): super().__init__() self. controller = controller self.init_ui() def init_ui(self): """初始化服务器管理界面""" self.setWindowTitle("ERP服务器管理") self.setGeometry(500, 200, 1000, 700) self.setFixedSize(1000, 700) # 创建中央部件 central_widget = QWidget() # 定义主窗口的中央显示区域 self.setCentralWidget(central_widget) # 创建主布局 垂直布局 main_layout = QVBoxLayout(central_widget) # 服务器设置,将下面的元素水平放置 settings_layout = QHBoxLayout() host_label = QLabel("主机:") self.host_edit = QLineEdit("0.0.0.0") port_label = QLabel("端口:") self.port_edit = QLineEdit("12808") self.start_button = QPushButton("启动服务器") self.start_button.clicked.connect(self.on_start_server) self.stop_button = QPushButton("停止服务器") self.stop_button.clicked.connect(self.on_stop_server) self.stop_button.setEnabled(False) settings_layout.addWidget(host_label) settings_layout.addWidget(self.host_edit) settings_layout.addWidget(port_label) settings_layout.addWidget(self.port_edit) # 用于设置伸缩量 隔开 settings_layout.addStretch() settings_layout.addWidget(self.start_button) settings_layout.addWidget(self.stop_button) # 将水平元素添加到主框架 main_layout.addLayout(settings_layout) # 连接信息表格 connections_label = QLabel("客户端连接:") main_layout.addWidget(connections_label) self.connections_table = QTableWidget() self.connections_table.setColumnCount(3) # 以后要添加用户名在这里 self.connections_table.setHorizontalHeaderLabels(["连接ID", "IP地址", "端口"]) # 可以将表格内容自动调整以填满整个窗口宽度 # self.connections_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) main_layout.addWidget(self.connections_table) # 日志区域 logs_label = QLabel("服务器日志:") main_layout.addWidget(logs_label) self.log_text = QTextEdit() self.log_text.setReadOnly(True) self.log_text.setFont(QFont("Courier New", 10)) main_layout.addWidget(self.log_text) # 设置状态栏 self.statusBar().showMessage("服务器已停止") def on_start_server(self): # print(f"Pool created in on_start: {id(asyncio.get_event_loop())}") """启动服务器按钮点击事件,这里要更改成获取本地ip地址""" # strip()去除开头和结尾的空白符 host = self.host_edit.text().strip() port = int(self.port_edit.text().strip()) if not host or port <= 0: QMessageBox.critical(self, "错误", "请输入有效的主机和端口") return # 将start_server加入到协程的调度里,在后台持续监听请求 asyncio.create_task(self.controller.start_server(host, port)) def on_stop_server(self): """停止服务器按钮点击事件""" asyncio.create_task(self.controller.stop_server()) def update_server_state(self, running): """更新服务器状态""" self.start_button.setEnabled(not running) self.stop_button.setEnabled(running) def add_connection(self, conn_id, ip, port): """添加连接信息""" row = self.connections_table.rowCount() self.connections_table.insertRow(row) self.connections_table.setItem(row, 0, QTableWidgetItem(str(conn_id))) self.connections_table.setItem(row, 1, QTableWidgetItem(ip)) self.connections_table.setItem(row, 2, QTableWidgetItem(str(port))) def remove_connection(self, conn_id): """移除连接信息""" for row in range(self.connections_table.rowCount()): if self.connections_table.item(row, 0).text() == str(conn_id): self.connections_table.removeRow(row) break def append_log(self, message): """添加日志消息""" timestamp = QDateTime.currentDateTime().toString("yyyy-MM-dd hh:mm:ss") self.log_text.append(f"[{timestamp}] {message}") def update_status(self, message): """更新状态栏""" self.statusBar().showMessage(message) def closeEvent(self, event): """重写关闭事件""" if self.stop_button.isEnabled(): reply = QMessageBox.question(self, '确认关闭', '服务器正在运行,请先停止服务器再关闭窗口,是否停止服务器?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: asyncio.create_task(self.controller.stop_server()) event.accept() else: event.ignore() else: event.accept() 代码三:import asyncio import logging from server.src.mvc.view.server_view import ServerView from server.src.mvc.model.server_model import ServerModel from aiohttp import web class ServerController: """服务器控制器""" def __init__(self): # 初始化视图界面 self.view = ServerView(self) # 初始化模型 self.model = ServerModel(self) # Application负责处理HTTP请求分发,通过规则将不同请求映射到对应的处理函数 self.app = web.Application() # 用来包装为可运行的服务器适配器 self.runner = None # 路由配置,监听请执行函数 self.setup_routes() self.site = None self.connections = {} self.next_conn_id = 1 async def start(self): """定义模型对数据库初始化""" await self.model.init_database() """显示服务器UI""" self.view.show() def setup_routes(self): """设置路由""" # 监听到客户端发送的/api/auth/login ,就执行self.handle_login self.app.router.add_post('/api/auth/login', self.handle_login) # 添加WebSocket连接处理 self.app.router.add_get('/ws', self.handle_websocket) async def start_server(self, host, port): """启动服务器""" print(f"Pool created in start_server开始: {id(asyncio.get_event_loop())}") try: # 将self.app包装为可运行的服务器适配器,AppRunner是异步启动服务器 self.runner = web.AppRunner(self.app) # 完成路由表、中间件的初始化等装备工作,setup是启动 await self.runner.setup() # web.TCPSite基于TCP的网络服务器,用于监听和接受HTTP请求 self.site = web.TCPSite(self.runner, host, port) print(f"服务器已成功启动并监听在 http://{host}:{port}") # 启动监听 await self.site.start() # 通知UI更新运行状态 self.view.update_server_state(True) # 记录日志 self.log_message(f"服务器已启动: http://{host}:{port}") self.update_status(f"服务器运行中: http://{host}:{port}") return True except Exception as e: self.log_message(f"启动服务器失败: {str(e)}") self.update_status(f"启动失败: {str(e)}") return False async def stop_server(self): """停止服务器""" try: if self.site: await self.site.stop() if self.runner: await self.runner.cleanup() self.log_message("服务器已停止") self.update_status("服务器已停止") self.view.update_server_state(False) except Exception as e: self.log_message(f"停止服务器失败: {str(e)}") self.update_status(f"停止失败: {str(e)}") return False async def handle_websocket(self, request): """处理webSocket连接""" # 创建WebSocket响应对象 ws = web.WebSocketResponse() # 处理协议升级 await ws.prepare(request) # 分配连接ID conn_id = self.next_conn_id self.next_conn_id += 1 # 记录连接信息 peername = request.transport.get_extra_info('peername') if peername: ip, port = peername self.connections[conn_id] = ws self.log_message(f"新连接: {ip}:{port} (ID:{conn_id})") self.view.add_connection(conn_id, ip, port) try: # 处理消息 async for msg in ws: if msg.type == web.WSMsgType.TEXT: self.log_message(f"收到来自连接 {conn_id} 的消息: {msg.data}") await ws.send_str(f"服务器收到: {msg.data}") elif msg.type == web.WSMsgType.ERROR: self.log_message(f"WebSocket连接 {conn_id} 发生错误: {ws.exception()}") finally: # 移除连接 if conn_id in self.connections: del self.connections[conn_id] self.log_message(f"连接断开: ID:{conn_id}") self.view.remove_connection(conn_id) return ws async def handle_login(self, request): """处理登录请求""" data = await request.json() print("data:", data) username = data.get('username') password = data.get('password') user = await self.model.authenticate_user(username, password) if user: return web.json_response({ 'success': True, 'data': { 'user_id': user.user_id, 'username': user.username, 'role': user.role } }) else: #web.json_response用于以 JSON 格式返回响应数据 return web.json_response({ 'success': False, 'error': '用户名或密码错误' }, status=401) def log_message(self, message): """记录日志""" self.view.append_log(message) def update_status(self, message): """更新状态""" self.view.update_status(message) async def stop(self): """停止服务器并清理资源""" await self.stop_server() 运行提示 RuntimeError: wrapped C/C++ object of type _SimpleTimer has been deleted
最新发布
07-12
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值