Хобрук: Ваш путь к мастерству в программировании

Сервер OpenSSL не получает запросы на мультимедиа (аудио/видео) от WKWebView

Цель, которую мы пытаемся достичь, — загрузить локальный видеоресурс (хранящийся на диске) в экземпляр WKWebView для использования в качестве текстуры в WebGL. До сих пор мы выполняли это, используя сервер, привязанный к локальному хосту (GCDWebServer), и загружая локальный исходный код в виде строки HTML с (в этом примере) baseURL:"http://localhost:8989/", а затем воспроизводить видео со следующей строкой кода:

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

Однако в связи с неизбежными изменениями в политике ATS Apple нам теперь нужно, чтобы это происходило через 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) и текст обслуживаются правильно; однако аудио и видео нет. Мы отслеживаем сетевой трафик в Чарльзе, и из веб-представления даже не поступают запросы. В частности, кроме запросов GET для изображений и javascript, в сокете не происходит никакой связи. Для изображений и текста мы видим запросы в Charles, а веб-представление получает обратный вызов проверки подлинности. Мы настроили нашу WKWebViewConfiguration, установив для «allowsInlineMediaPlayback» значение «YES» и для «mediaPlaybackRequiresUserAction» значение «NO». Может кто-нибудь объяснить, почему из веб-представления запускаются только некоторые запросы?



Ответы:


1

У нас был httpListener на "http://+:13333", но ничего не происходит, когда мы загружаем нашу htmlString в WKWebView с помощью этот контент "http://localhost:13333".

Наше решение состояло в том, чтобы заменить «localhost» на «127.0.0.1».

Может быть, это общая проблема WKWebView?!

08.08.2017
Новые материалы

Решения DBA Metrix
DBA Metrix Solutions предоставляет удаленного администратора базы данных (DBA), который несет ответственность за внедрение, обслуживание, настройку, восстановление базы данных, а также другие..

Начало работы с Блум
Обзор и Codelab для генерации текста с помощью Bloom Оглавление Что такое Блум? Некоторые предостережения Настройка среды Скачивание предварительно обученного токенизатора и модели..

Создание кнопочного меню с использованием HTML, CSS и JavaScript
Вы будете создавать кнопочное меню, которое имеет состояние наведения, а также позволяет вам выбирать кнопку при нажатии на нее. Финальный проект можно увидеть в этом Codepen . Шаг 1..

Внедрите OAuth в свои веб-приложения для повышения безопасности
OAuth — это широко распространенный стандарт авторизации, который позволяет приложениям получать доступ к ресурсам от имени пользователя, не раскрывая его пароль. Это позволяет пользователям..

Классы в JavaScript
class является образцом java Script Object. Конструкция «class» позволяет определять классы на основе прототипов с чистым, красивым синтаксисом. // define class Human class Human {..

Как свинг-трейдеры могут использовать ИИ для больших выигрышей
По мере того как все больше и больше профессиональных трейдеров и активных розничных трейдеров узнают о возможностях, которые предоставляет искусственный интеллект и машинное обучение для улучшения..

Как построить любой стол
Я разработчик программного обеспечения. Я люблю делать вещи и всегда любил. Для меня программирование всегда было способом создавать вещи, используя только компьютер и мое воображение...