OpenSSL服务器不接收来自WKWebView的媒体(audio/video)请求

我们试图完成的目标是将本地video资源(存储在磁盘上)加载到WKWebView实例中,以用作WebGL中的纹理。 到目前为止,我们已经使用绑定到localhost的服务器(GCDWebServer)来完成这个任务,并且将本地源代码作为HTMLstring(在本例中为baseURL:“ http:// localhost:8989 / ”)加载,然后用以下代码行播放video:

<video src="test.mp4" width="320" height="240" preload="auto" playsinline autoplay muted></video> 

但是,随着苹果ATS政策的即将发生的变化,我们现在需要通过HTTPS来实现这一点。 我们新的服务器实现基于OpenSSL,包含如下:

 #import "SSLServer.h" #import "Logging.h" #import "Util.h" #import "SSLServerResponse.h" #include <errno.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <resolv.h> #include "openssl/ssl.h" #include "openssl/err.h" #define FAIL -1 #define SSL_SERVER_UPDATE_INTERVAL 0.0f @interface SSLServer () { BOOL _keepAlive; NSUInteger _port; NSString* _directoryPath; NSString* _certFilepath; SSLServerStartCompletionHandler _startCompletionHandler; } @end @implementation SSLServer -(instancetype)initWithPort:(NSUInteger)port directoryPath:(NSString*)directoryPath andCertFilepath:(NSString*)certFilepath { self = [super init]; if (self) { _keepAlive = YES; _port = 8989; //port; _certFilepath = certFilepath ? certFilepath : @""; _directoryPath = directoryPath; } return self; } -(void)startWithCompletionHandler:(SSLServerStartCompletionHandler)handler { _startCompletionHandler = handler; SSL_CTX *ctx; int server; SSL_library_init(); //Initialize SSL ctx = [self initServerCTX]; if (ctx == NULL) { LogInfoPrivate(@"[SSLServer] : Failed to create SSL context for some reason"); return; } //Load certs [self loadCertificates:ctx certFile:_certFilepath keyFile:_certFilepath]; //Create server socket server = [self openListener:_port]; if (server == -1) { _startCompletionHandler(NO, (NSUInteger)_port); } else { _startCompletionHandler(YES, (NSUInteger)_port); } _startCompletionHandler = nil; while (_keepAlive) { struct sockaddr_in addr; socklen_t len = sizeof(addr); SSL *ssl; LogInfoPrivate(@"[SSLServer] : Listening on port: %lu", (unsigned long)_port); //Accept connection as usual int client = accept(server, (struct sockaddr*)&addr, &len); LogInfoPrivate(@"[SSLServer] : Connection: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); LogInfoPrivate(@"[SSLServer] : Port:%lu\n", (unsigned long)_port); //Get new SSL state with context ssl = SSL_new(ctx); //Set connection socket to SSL state SSL_set_fd(ssl, client); //Service connection dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ [self servlet:ssl directoryPath:_directoryPath]; }); [NSThread sleepForTimeInterval:SSL_SERVER_UPDATE_INTERVAL]; } //Close server socket close(server); //Release context SSL_CTX_free(ctx); } -(int)openListener:(NSUInteger)port { int sock; struct sockaddr_in addr; sock = socket(PF_INET, SOCK_STREAM, 0); bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons((unsigned long)port); addr.sin_addr.s_addr = INADDR_ANY; socklen_t len = sizeof(addr); if (bind(sock, (struct sockaddr*)&addr, len) != 0) { LogErrorPrivate(@"[SSLServer] : Can't bind port"); return -1; } if (listen(sock, 10) != 0) { LogErrorPrivate(@"[SSLServer] : Can't configure listening port"); return -1; } // when sin_port is init'd as '0', socket lib will randomize the port, so, we grab the bound port here. if (port == 0) { if (getsockname(sock, (struct sockaddr *)&addr, &len) == -1) { LogErrorPrivate(@"[SSLServer] : Could not retrieve random port number"); return -1; } else { _port = ntohs(addr.sin_port); } } return sock; } -(SSL_CTX*)initServerCTX { const SSL_METHOD *method; SSL_CTX *ctx; //Load & register all cryptos, etc. OpenSSL_add_all_algorithms(); //Load all error 4messages SSL_load_error_strings(); //Create new server-method instance method = TLSv1_2_server_method(); //Create new context from method ctx = SSL_CTX_new(method); if (ctx == NULL) { ERR_print_errors_fp(stderr); return NULL; } return ctx; } -(void)loadCertificates:(SSL_CTX*)ctx certFile:(NSString*)certFile keyFile:(NSString*)keyFile { //Set the local certificate from CertFile if (SSL_CTX_use_certificate_file(ctx, [certFile UTF8String], SSL_FILETYPE_PEM) <= 0 ) { ERR_print_errors_fp(stderr); return; } //Set the private key from KeyFile (may be the same as CertFile) if (SSL_CTX_use_PrivateKey_file(ctx, [keyFile UTF8String], SSL_FILETYPE_PEM) <= 0 ) { ERR_print_errors_fp(stderr); return; } //Verify private key if (!SSL_CTX_check_private_key(ctx) ) { LogErrorPrivate(@"[SSLServer] : Private key does not match the public certificate"); return; } } -(void)showCerts:(SSL*)ssl { X509 *cert; char *line; //Get certificates (if available) cert = SSL_get_peer_certificate(ssl); if (cert != NULL) { LogInfoPrivate(@"[SSLServer] : Server certificates:"); line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); LogInfoPrivate(@"[SSLServer] : Subject: %s", line); free(line); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); LogInfoPrivate(@"[SSLServer] : Issuer: %s", line); free(line); X509_free(cert); } else { LogInfoPrivate(@"[SSLServer] : No certificates."); } } //Serve the connection -(void)servlet:(SSL*)ssl directoryPath:(NSString*)directoryPath { char buf[1024]; int sd, bytes; //Do SSL-protocol accept if (SSL_accept(ssl) == FAIL) { ERR_print_errors_fp(stderr); } else { //Get any certificates [self showCerts:ssl]; //Get request bytes = SSL_read(ssl, buf, sizeof(buf)); if (bytes > 0) { buf[bytes] = 0; LogInfoPrivate(@"[SSLServer] Client msg: \"%s\"", buf); NSArray* requestElements = [[NSString stringWithUTF8String:buf] componentsSeparatedByString:@" "]; NSString* resource = [requestElements[1] lastPathComponent]; NSString* resourcePath = [directoryPath stringByAppendingPathComponent:resource]; LogInfoPrivate(@"[SSLServer] Resource path: %@", resource); //Configure the response SSLServerResponse* response = [[SSLServerResponse alloc] initWithResourcePath:resourcePath chunked:YES]; [response configure]; //Write the response for (NSData* data in response.payload) { SSL_write(ssl, (const char*)[data bytes], (int)[data length]); } } else { LogInfoPrivate(@"[SSLServer] : Nothing to send back"); ERR_print_errors_fp(stderr); } } //Get socket connection sd = SSL_get_fd(ssl); //Release SSL state SSL_free(ssl); //Close connection close(sd); } -(void)finish { _keepAlive = NO; } @end 

我们遇到的问题是图像(.png)和文本服务正确; 但是,audio和video不是。 我们一直在监控Charles的networkingstream量,甚至没有任何请求出现在Web视图中。 更具体地说,除了对图像和javascript的GET请求之外,在套接字上不发生通信。 对于图片和文字,我们可以在Charles中看到请求,Web视图正在获取身份validation挑战callback。 我们通过将'allowInlineMediaPlayback'设置为'YES'和'mediaPlaybackRequiresUserAction'为'NO'来configuration我们的WKWebViewConfiguration。 有人可以解释为什么只有一些请求正在从networking视图中被解雇?

我们在“ http:// +:13333 ”上有一个httpListener,但是当我们在WKWebView中加载这个内容为“ http:// localhost:13333 ”的htmlString时,会发生nothings。

我们的解决scheme是用“127.0.0.1”取代“localhost”。

也许这是WKWebView的通病?