嵌入Flutter的Golang服务器

huangapple go评论110阅读模式
英文:

embedding golang server with flutter

问题

我有一个使用golang编写的Web服务器,它使用了graphql包gqlgengorm用于数据库。

由于golang可以在Android上编译和运行,我想创建一个离线版本的应用程序,其中可以使用SQLite进行离线存储,并将整个服务器作为aar导入。

我已经成功构建了aar,并按照这里的说明将其添加到我的Flutter应用程序中,使用gomobile启动。

当我运行我的应用程序时,服务器在Android上启动,似乎工作正常,当在模拟器的Chrome应用中打开http://localhost:8080/时,我可以看到GraphQL Playground正常运行,就像在Windows的浏览器中看到的一样。

我面临的唯一问题是,Flutter应用程序在服务器后台运行时只显示一个空白屏幕。以下是启动应用程序时打印的日志:

Launching lib\main.dart on sdk gphone64 x86 64 in debug mode...
Running Gradle task 'assembleDebug'...
√  Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Debug service listening on ws://127.0.0.1:62561/DyGpOhyuekw=/ws
Syncing files to device sdk gphone64 x86 64...
I/GoLog   ( 6295): connect to http://localhost:8080/ for GraphQL playground
W/ux.offline( 6295): type=1400 audit(0.0:38): avc: denied { read } for name="somaxconn" dev="proc" ino=74990 scontext=u:r:untrusted_app:s0:c149,c256,c512,c768 tcontext=u:object_r:proc_net:s0 tclass=file permissive=0 app=com.nux.offline

我认为问题可能出在上面的日志avc: denied { read } for name="somaxconn",或者有些东西导致了UI线程的阻塞,因为Flutter似乎没有渲染任何内容。

我正在使用Flutter插件启动服务器,以下是ServerPlugin.kt的代码:

package com.mahesabu.server.server

import androidx.annotation.NonNull

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import lib.Lib.startServer

/** ServerPlugin */
class ServerPlugin : FlutterPlugin, MethodCallHandler {
    private lateinit var channel: MethodChannel

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "server")
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (call.method == "startServer") {
            try {
                val port = startServer()
                result.success(port)
            } catch (e: Exception) {
                e.printStackTrace();
                result.error("Error in starting server", "${e.message}", null);
            }
        } else {
            result.notImplemented()
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}

这是Dart代码:

class Server {
  static const MethodChannel _channel = MethodChannel('server');

  static Future<String?> startServer() async {
    try {
      final String? port = await _channel.invokeMethod('startServer');
      return port;
    } catch (e) {
      log('startServer error: ${e.toString()}');
      return null;
    }
  }
}

我的应用程序的主要部分如下:

import 'package:flutter/material.dart';
import 'package:server/server.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final port = await Server.startServer();

  print('port $port');

  runApp(const MyApp());
}

在golang方面,这是我启动服务器的方式:

//start.go
package util

import (
	"github.com/99designs/gqlgen/graphql/handler"
	"github.com/99designs/gqlgen/graphql/playground"
	"log"
	"my-project/graph"
	"my-project/graph/generated"
	"net/http"
	"os"
)

const defaultPort = "8080"

func StartServer(Offline bool) string {
	port := os.Getenv("PORT")
	if port == "" {
		port = defaultPort
	}

	db := InitDB(Offline)

	config := generated.Config{Resolvers: &graph.Resolver{
		DB:     db,
	}}

	srv := handler.NewDefaultServer(generated.NewExecutableSchema(config))

	http.Handle("/", playground.Handler("GraphQL playground", "/query"))
	http.Handle("/query", srv)

	log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
	log.Fatal(http.ListenAndServe(":"+port, nil))

	return port
}

这是用于生成与gomobile绑定的库的代码:

//lib.go
package lib

import "my-project/util"

func StartServer() string {
	return util.StartServer(true)
}

希望能帮助你解决问题。

编辑

我认为问题出现在嵌入式服务器尝试创建一个新端口时。我不知道是否可能让应用程序在Android上像Node.js、Golang那样打开一个新端口,例如http://localhost:8080。

现在我认为如果有一种方法可以创建端口,那么我就可以成功运行我的应用程序,但我不知道具体的方法。

我在考虑是否可以找到Android上的任何可用端口,并将其用于启动服务器,也许这个堆栈可能是可行的。在Kotlin中,类似这样的代码可能会在查找端口时起作用:

import java.net.ServerSocket

fun main() {
   val serverPort = ServerSocket(0)
   print(serverPort.toString())
}

但是,当我尝试类似的操作时,它会导致Android应用程序崩溃。

我在GitHub上上传了一个仓库,展示了我打算做的事情。它只是一个使用gin的简单golang服务器和一个没有Flutter的Android应用程序(在这里可用)。

英文:

I have web server written in golang which uses graphql package gqlgen and gorm for database.

Since golang can be compiled and run on android I wanted to create offline version of my app where sqlite can be used for offline storage and import my whole server as an aar.

I have successfully built aar and add it on my flutter using gomobile by following instructions here

When I run my app, server is started on android it seems to work just fine and when opening http://localhost:8080/ on emulator's chrome app I am seeing graphql playground runs without any problem just like I see on browser in windows.

The only problem I face is that flutter app just shows a blank screen while server runs in background. The following are the logs printed when app is started

Launching lib\main.dart on sdk gphone64 x86 64 in debug mode...
Running Gradle task &#39;assembleDebug&#39;...
√  Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Debug service listening on ws://127.0.0.1:62561/DyGpOhyuekw=/ws
Syncing files to device sdk gphone64 x86 64...
I/GoLog   ( 6295): connect to http://localhost:8080/ for GraphQL playground
W/ux.offline( 6295): type=1400 audit(0.0:38): avc: denied { read } for name=&quot;somaxconn&quot; dev=&quot;proc&quot; ino=74990 scontext=u:r:untrusted_app:s0:c149,c256,c512,c768 tcontext=u:object_r:proc_net:s0 tclass=file permissive=0 app=com.nux.offline

I think maybe problem lies on the above logs avc: denied { read } for name=&quot;somaxconn&quot; or something is causing the blocking of ui thread since its like flutter don't render a thing.

I am using flutter plugin to start server and this is ServerPlugin.kt

package com.mahesabu.server.server

import androidx.annotation.NonNull

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import lib.Lib.startServer

/** ServerPlugin */
class ServerPlugin : FlutterPlugin, MethodCallHandler {
    /// The MethodChannel that will the communication between Flutter and native Android
    ///
    /// This local reference serves to register the plugin with the Flutter Engine and unregister it
    /// when the Flutter Engine is detached from the Activity
    private lateinit var channel: MethodChannel

    override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, &quot;server&quot;)
        channel.setMethodCallHandler(this)
    }

    override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (call.method == &quot;startServer&quot;) {
            try {
                val port = startServer() //from golang bindings
                result.success(port)
            } catch (e: Exception) {
                e.printStackTrace();
                result.error(&quot;Error in starting server&quot;, &quot;${e.message}&quot;, null);
            }
        } else {
            result.notImplemented()
        }
    }

    override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
    }
}

And this is is dart code

class Server {
  static const MethodChannel _channel = MethodChannel(&#39;server&#39;);

  static Future&lt;String?&gt; startServer() async {
    try {
      final String? port = await _channel.invokeMethod(&#39;startServer&#39;);
      return port;
    } catch (e) {
      log(&#39;startServer error: ${e.toString()}&#39;);
      return null;
    }
  }
}

and my app's main is as follows

import &#39;package:flutter/material.dart&#39;;
import &#39;package:server/server.dart&#39;;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final port = await Server.startServer(); //problem

  print(&#39;port $port&#39;); //This don&#39;t print

  runApp(const MyApp());
}

On go side this how i start server

//start.go
package util

import (
	&quot;github.com/99designs/gqlgen/graphql/handler&quot;
	&quot;github.com/99designs/gqlgen/graphql/playground&quot;
	&quot;log&quot;
	&quot;my-project/graph&quot;
	&quot;my-project/graph/generated&quot;
	&quot;net/http&quot;
	&quot;os&quot;
)

const defaultPort = &quot;8080&quot;

// StartServer This way so that it can be invoked via libs
func StartServer(Offline bool) string {
	port := os.Getenv(&quot;PORT&quot;)
	if port == &quot;&quot; {
		port = defaultPort
	}

	db := InitDB(Offline)

	config := generated.Config{Resolvers: &amp;graph.Resolver{
		DB:     db,
	}}

	srv := handler.NewDefaultServer(generated.NewExecutableSchema(config))

	http.Handle(&quot;/&quot;, playground.Handler(&quot;GraphQL playground&quot;, &quot;/query&quot;))
	http.Handle(&quot;/query&quot;, srv)

	log.Printf(&quot;connect to http://localhost:%s/ for GraphQL playground&quot;, port)
	log.Fatal(http.ListenAndServe(&quot;:&quot;+port, nil))

	return port
}

and this is lib for generating bindings with gomobile

//lib.go
package lib
import &quot;my-project/util&quot;
// StartServer This way so that it can be invoked via libs
func StartServer() string {
return util.StartServer(true)
}

Any help on fixing this will be appreciated.

Edit

I think problem occurs when embedded server tries to create a new port. I don't know if it is possible for an app to open a new port in android just like nodejs, golang open things like http://localhost:8080.

Now I think if there is a way to create port then I can run my app successfully but I don't know how exactly.

I was thinking if I can find any available port on android and use to start server maybe this stack could be possible. In kotlin something like this may work in finding port.

import java.net.ServerSocket

fun main() {
   val serverPort = ServerSocket(0)
   print(serverPort.toString())
}

but it crashes on android app when I try similar thing.

I have uploaded a repository on GitHub showing what I intend to do. It's just a simple golang server using gin and android app (with no flutter) it is available here.

答案1

得分: 2

我不知道是否可能像Node.js、Golang那样,在Android上的应用程序中打开一个新的端口,比如localhost:8080。为了找出问题的根本原因,你可以尝试在Android上运行一个HTTP服务器,比如https://stackoverflow.com/questions/6329468。如果成功了,那就试着找出它们在处理端口方面的差异。此外,请确保在androidmanifest.xml文件中有正确的权限设置。(根据我的评论重新表述)

英文:

> "I don't know if it is possible for an app to open a new port in android just like nodejs, golang open things like localhost:8080"

To find out the root cause, try to run an HTTP server in android, such as https://stackoverflow.com/questions/6329468. If that succeeds, try to find the differences about how they deal with ports.

In addition, please be sure you have correct permission in androidmanifest.xml.

(Rephrased from my comments)

huangapple
  • 本文由 发表于 2022年4月9日 22:37:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/71809021.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定