flutter_inappwebview refused local https connection in flutter app
I'm trying to serve local content from assets over https in order to gain access to features like webrtc that require SSL.
Since the local app server provided in flutter_inappwebview cannot handle ssl connections, I replaced the InAppLocalHostServer class with InAppLocalHostSecureServer with the following code:
import 'dart:io';
import 'dart:async';
import 'package:flutter/services.dart' show rootBundle;
import 'package:mime/mime.dart';
class InAppLocalHostSecureServer {
HttpServer _server;
int _port = 8443;
InAppLocalHostSecureServer({int port = 8443}) {
this._port = port;
}
///Starts a server on http://localhost:[port]/.
///
///**NOTE for iOS**: For the iOS Platform, you need to add the `NSAllowsLocalNetworking` key with `true` in the `Info.plist` file (See [ATS Configuration Basics](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35)):
///```xml
///<key>NSAppTransportSecurity</key>
///<dict>
/// <key>NSAllowsLocalNetworking</key>
/// <true/>
///</dict>
///```
///The `NSAllowsLocalNetworking` key is available since **iOS 10**.
Future<void> start() async {
if (this._server != null) {
throw Exception('Server already started on https://localhost:$_port');
}
var completer = Completer();
runZoned(() async {
SecurityContext context = new SecurityContext();
var chain = await rootBundle.load('assets/certificates/cert.pem');
var key = await rootBundle.load('assets/certificates/key.pem');
context.useCertificateChainBytes(chain.buffer.asInt8List());
context.usePrivateKeyBytes(key.buffer.asInt8List(), password: 'dartdart');
HttpServer.bindSecure('127.0.0.1', _port, context).then((server) {
print('Server running on https://localhost:' + _port.toString());
this._server = server;
server.listen((HttpRequest request) async {
print(request);
var body = List<int>();
var path = request.requestedUri.path;
path = (path.startsWith('/')) ? path.substring(1) : path;
path += (path.endsWith('/')) ? 'index.html' : '';
try {
body = (await rootBundle.load(path)).buffer.asUint8List();
} catch (e) {
print(e.toString());
request.response.close();
return;
}
var contentType = ['text', 'html'];
if (!request.requestedUri.path.endsWith('/') &&
request.requestedUri.pathSegments.isNotEmpty) {
var mimeType =
lookupMimeType(request.requestedUri.path, headerBytes: body);
if (mimeType != null) {
contentType = mimeType.split('/');
}
}
request.response.headers.contentType =
ContentType(contentType[0], contentType[1], charset: 'utf-8');
request.response.add(body);
request.response.close();
});
completer.complete();
});
}, onError: (e, stackTrace) {
print('Error: $e $stackTrace');
});
return completer.future;
}
///Closes the server.
Future<void> close() async {
if (this._server != null) {
await this._server.close(force: true);
print('Server running on http://localhost:$_port closed');
this._server = null;
}
}
}
Most of the code is a copy-paste of the original class.
What I changed was that I called HttpServer.bindSecure instead of HttpServer.bind and provided the openssl certificate and key.
There doesn't seem to be an error logged in the console when the server starts, but I can't access it.
Here is the client code trying to access the local URL:
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'InAppLocalHostSecureServer.dart';
class WebAudioTest extends StatefulWidget {
@override
_WebAudioTestState createState() => _WebAudioTestState();
}
class _WebAudioTestState extends State<WebAudioTest> {
InAppWebViewController webView;
InAppLocalHostSecureServer localhostServer;
String url = "https://127.0.0.1:8443/assets/web/index.html";
@override
void initState() {
super.initState();
this.init();
}
void init() async {
this.localhostServer = new InAppLocalHostSecureServer();
await localhostServer.start();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Web Audio Test'),
),
body: InAppWebView(
initialUrl: url,
initialHeaders: {},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
)),
onWebViewCreated: (InAppWebViewController c) {
webView = c;
},
onConsoleMessage: (controller, consoleMessage) {
print("CONSOLE MESSAGE: " + consoleMessage.message);
},
),
);
}
}
No errors are shown in the console, but the page is cluttered with the following error message:
净:: ERR_CONNECTION_REFUSED
Any help is welcome.
Well, to answer my own question:
The problem I'm having is simply building the InAppWebView prematurely before the server startup is complete. The solution is simple, just set the flag to true when starting the server, then create the InAppWebView only when the flag is true.
Other than that, WebRTC works without https on localhost, I tested it on Android and iOS. So local https is not required for this use case.
But anyway, if you need someone to serve https native content for any other reason, the code in this article can serve as a basis for that.