GCDWebServer 源码解读

本文主要介绍GCDWebServer内部的处理过程,关于GCDWebServer的使用可以参考GCDWebServer GitHub

GCDWebServer的处理流程分为三个步骤:
- 启动&&监听请求
- 处理请求
- 响应

启动&&监听请求

GCDWebServer提供了若干个启动方法:

- (BOOL)start;
- (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
- (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name;
- (BOOL)runWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error;

这些启动方法,最终都会调用_start:方法,下面看下_start:方法中到底做了什么:

- (BOOL)_start:(NSError**)error {
  GWS_DCHECK(_source4 == NULL);
  //获取外部配置的服务端的监听端口
  NSUInteger port = [_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue];
  //表示是否是使用 127.0.0.1 或 localhost 的形式访问服务器
  BOOL bindToLocalhost = [_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue];
  //等待连接队列中连接的最大数量
  NSUInteger maxPendingConnections = [_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue];
  //服务器listen socket的本地地址信息的设置
  struct sockaddr_in addr4;
  bzero(&addr4, sizeof(addr4));
  addr4.sin_len = sizeof(addr4);
  addr4.sin_family = AF_INET;
  addr4.sin_port = htons(port);
  addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY);
  //创建 listen scoket
  int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error];
  if (listeningSocket4 <= 0) {
    return NO;
  }
  //如果外部设置的端口为0 那么将由系统分配一个可用端口
  if (port == 0) {
    struct sockaddr_in addr;
    socklen_t addrlen = sizeof(addr);
    if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) {
      port = ntohs(addr.sin_port);
    } else {
      GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno);
    }
  }
  //作用同IPv4 只是用于支持IPv6
  struct sockaddr_in6 addr6;
  bzero(&addr6, sizeof(addr6));
  addr6.sin6_len = sizeof(addr6);
  addr6.sin6_family = AF_INET6;
  addr6.sin6_port = htons(port);
  addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any;
  int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error];
  if (listeningSocket6 <= 0) {
    close(listeningSocket4);
    return NO;
  }

  _serverName = [_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy];
  //获取外部设置的 http 认证类型(参考下面备注)
  NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil);
  //保存用于基本认证的账号密码
  if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) {
    _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
    _authenticationBasicAccounts = [[NSMutableDictionary alloc] init];
    NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
    [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
      [_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username];
    }];
  }
  //保存用于摘要认证的账号密码
  else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) {
    _authenticationRealm = [_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy];
    _authenticationDigestAccounts = [[NSMutableDictionary alloc] init];
    NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{});
    [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) {
      [_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, _authenticationRealm, password) forKey:username];
    }];
  }
  _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]);
  _shouldAutomaticallyMapHEADToGET = [_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue];
  _disconnectDelay = [_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue];
  _dispatchQueuePriority = [_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue];

  //创建对 listen scoket 是否有可读内容的监听source(即是否有客户端的连接请求产生)
  _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO];
  _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES];
  _port = port;
  _bindToLocalhost = bindToLocalhost;

  NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil);
  NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp");
  //Bonjour 服务
  if (bonjourName) {
    _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port);
    if (_registrationService) {
      CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL};

      CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context);
      CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
      CFStreamError streamError = {0};
      CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError);

      _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService);
      if (_resolutionService) {
        CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context);
        CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes);
      } else {
        GWS_LOG_ERROR(@"Failed creating CFNetService for resolution");
      }
    } else {
      GWS_LOG_ERROR(@"Failed creating CFNetService for registration");
    }
  }
  //NAT服务
  if ([_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) {
    DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self);
    if (status == kDNSServiceErr_NoError) {
      CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL};
      _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context);
      if (_dnsSocket) {
        CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate);
        _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0);
        if (_dnsSource) {
          CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes);
        } else {
          GWS_LOG_ERROR(@"Failed creating CFRunLoopSource");
          GWS_DNOT_REACHED();
        }
      } else {
        GWS_LOG_ERROR(@"Failed creating CFSocket");
        GWS_DNOT_REACHED();
      }
    } else {
      GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status);
    }
  }
  //开始监听 listen socket
  dispatch_resume(_source4);
  dispatch_resume(_source6);
  GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL);
  if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [_delegate webServerDidStart:self];
    });
  }

  return YES;
}

更多关于socket编程,可以参考这篇博客
关于 HTTP 认证,可以参考 <<HTTP 权威指南>> 的12、13章

创建监听 listen socket 的source的方法:

- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 {
  dispatch_group_enter(_sourceGroup);
  //创建一个监听I/O是否可读的source
  dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0));
  //设置source取消监听逻辑
  dispatch_source_set_cancel_handler(source, ^{

    @autoreleasepool {
      int result = close(listeningSocket);
      if (result != 0) {
        GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
      } else {
        GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket);
      }
    }
    dispatch_group_leave(_sourceGroup);

  });
  //设置source监听到事件后的处理逻辑
  dispatch_source_set_event_handler(source, ^{

    @autoreleasepool {
      struct sockaddr_storage remoteSockAddr;
      socklen_t remoteAddrLen = sizeof(remoteSockAddr);
      //与客户端建立连接,此处生成的socket称为:connect socket
      int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen);
      //连接如果建立成功
      if (socket > 0) {
        /*
            获取连接者的地址信息
        */
        NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen];

        struct sockaddr_storage localSockAddr;
        socklen_t localAddrLen = sizeof(localSockAddr);
        NSData* localAddress = nil;
        if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) {
          localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen];
          GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6));
        } else {
          GWS_DNOT_REACHED();
        }

        int noSigPipe = 1;
        //(此处设置原因参考下面的备注)
        setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe));  // Make sure this socket cannot generate SIG_PIPE

        GCDWebServerConnection* connection = [[_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket];  // Connection will automatically retain itself while opened
        [connection self];  // Prevent compiler from complaining about unused variable / useless statement
      } else {
        GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno);
      }
    }

  });
  return source;
}

关于 SIG_PIPE 信号

当在一个已经断开的socket的上调用send函数发送数据会产生SIG_PIPE信号,系统接收到该信号后会将App进程终止。假设App没有崩溃,那么
send函数会返回一个-1,我们可以通过该值判断发送失败,然后重新建立链接,但是SIG_PIPE信号发生在send函数返回之前,所以我们需要通过设置SO_NOSIGPIPE选项让socket不要
发射SIG_PIPE信号。

至此,服务器已经处于监听状态,并设置好了监听到连接后的处理逻辑。

处理请求

读取header

上一节中,在监听到连接请求并建立完连接后,便会开始处理请求,首先肯定是要从连接中把 header 部分数据读取出来。

从哪里读

connect socket中读取,也就是accept()函数返回的那个socket,也就是服务端与客户端的那条连接。

怎么读

在Unix-like的系统中,一切皆文件,所以socket也是文件,accpet()函数返回的其实是个用于操作对应socket的文件描述符(File Descriptor 简称:FD)。
GCDWebServer中使用dispatch_read读取数据,调用dispatch_read时,会传入一个handler,当读取到数据或读取出错时,该handler会被调用。
那么什么时候表示header读取完毕了呢?根据http的规范,headerbody部分通过CRLF(\r\n\r\n)进行分隔,所以每读取一部分数据后,都会判断下当前数据中是否有CRLF,如果有则表示header读取完毕了。

这种读取方式有个需要注意的地方,因为读取header的时候,每次都是读取尽量多的数据,那么,如果此次读取时,socket中如果已经有了部分body的数据,则会被一同读取到,
所以,header读取完后,如果发现CRLF后面还有其他数据,那么会将这些数据保存下来,之后会作为body数据的一部分。

读取header的逻辑在GCDWebServerConnection类的_readRequestHeaders方法中。

header读取完毕后的处理

header读取完毕后并不会立即就开始读取body数据,还要进行一些header字段校验的工作,以确保这是一条有效的、服务端支持的http请求(因为http中header字段非常多,服务器有时候
并不会支持所有字段,对于不支持的字段则需要将报错信息响应给客户端)。下面用一个流程图表示header读取完后在读取body数据前的处理:
这里写图片描述

读取body

从哪里读

当然,还是从connect socket中读取,只不过body数据读取的起点是从上一步的header数据读取的终点开始。

怎么读

这里会涉及到两个概念: 内容编码(Content-Encoding) 和 传输编码(Transfer-Encoding)。

内容编码介绍 —– 摘自<<HTTP 权威指南>>

HTTP 应用程序有时在发送之前需要对内容进行编码。例如,在把很大的 HTML 文 档发送给通过慢速连接连上来的客户端之前 , 服务器可能会对它进行压缩,这样有 助于减少传输实体的时间。服务器还可以把内容搅乱或加密,以此来防止未经授权 的第三方看到文档的内容。
这种类型的编码是在发送方应用到内容之上的。当内容经过内容编码之后,编好码 的数据就放在实体主体中,像往常一样发送给接收方。

HTTP 定义了一些标准的内容编码类型,并允许用扩展编码的形式增添更多的编码。 由互联网号码分配机构(IANA)对各种编码进行标准化,它给每个内容编码算法分 配了唯一的代号。Content-Encoding 首部就用这些标准化的代号来说明编码时使 用的算法。
gzip : 表明实体采用 GNU zip 编码
compress : 表明实体采用 Unix 的文件压缩程序
deflate : 表明实体是用 zlib 的格式压缩的
identity : 表明没有对实体进行编码。当没有 Content-Encoding 首部时,就默认 为这种情况

关于HTTP内容编码更详细的介绍可参考<<HTTP 权威指南>> 15.5 章节。

传输编码介绍 —– 摘自<<HTTP 权威指南>>

传输编码也是作用在实体主体上的可逆变换,但使用它们是由 于架构方面的原因,同内容的格式无关。使用传输编码是为了改变 报文中的数据在网络上传输的方式。

长久以来,在其他一些协议中会用传输编码来保证报文经过网络时能得到“可靠传 输”。在 HTTP 协议中,可靠传输关注的焦点有所不同,因为底层的传输设施已经标 准化并且容错性更好。在 HTTP 中,只有少数一些情况下,所传输的报文主体可能 会引发问题。其中两种情况如下所述。

未知的尺寸

如果不先生成内容,某些网关应用程序和内容编码器就无法确定报文主体的最终 大小。通常,这些服务器希望在知道大小之前就开始传输数据。因为 HTTP 协议 要求 Content-Length 首部必须在数据之前,有些服务器就使用传输编码来发送数据,并用特别的结束脚注表明数据结束。

安全性

你可以用传输编码来把报文内容扰乱,然后在共享的传输网络上发送。不过,由 于像 SSL 这样的传输层安全体系的流行,就很少需要靠传输编码来实现安全性了。

关于HTTP传输编码更详细的介绍可参考<<HTTP 权威指南>> 15.6 章节。

这里需要注意的是,客户端在发出http报文时,是先进行内容编码再进行传输编码的,这个很好理解: 内容确定了,才会开始传输。
因此,在读取body数据的时候则与发送http报文时相反,先根据传输编码读取body数据,再根据内容编码解码body数据。

body 数据存哪儿

GCDWebServer中,GCDWebServerRequest类是用来包装请求信息的,所以body数据都会被”写”到GCDWebServerRequest实例中,因为body数据会有各种格式而且服务端对不同请求的body数据的处理也可能各不相同,所以 GCDWebServer中提供了若干个GCDWebServerRequest的子类,以方便的格式化或存储body数据:
-GCDWebServerDataRequest: 将 body 的数据以原始data的形式保存,并且提供了text、jsonObject等便捷属性用来获取解码后的body数据。
-GCDWebServerFileRequest: body 读取完毕后,会将body数据保存在磁盘上,并会提供文件保存的位置。
-GCDWebServerMultiPartFormRequest:用来解析Content-Typemultipart/form-data时的body数据。
-GCDWebServerURLEncodedFormRequest:用来解析Content-Typeapplication/x-www-form-urlencoded时的body数据。

代码说明
读取body的方式会根据header中是否设置了Transfer-Encoding: chunked而不同,GCDWebServer封装了两个读取body数据的方法: _readBodyWithLength:initialData: _readChunkedBodyWithInitialData:,下面依次说明下这两个方法:
_readBodyWithLength:initialData:
/*
header 中未设置 Transfer-Encoding: chunked 时,使用这个方法读取 body 数据
注意第二个参数 initialData,它是读取 header 时额外读取到的 body 数据。
*/
- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData {
  NSError* error = nil;
  /*
  前面讲过,body 的数据会被"写"到 GCDWebServerRequest 实例中,所以 GCDWebServerRequest 内部有一个 _writer 属性,
  它是 id<GCDWebServerBodyWriter> 类型,专门用来负责将读取到的 body 数据"写"入到 GCDWebServerRequest 中,其会根据
  Content-Encoding 对body 数据进行解码。

  目前 GCDWebServer 中只支持gzip的解码,就是说只支持 Content-Encoding: gzip的情况,GCDWebServer 中 GCDWebServerGZipDecoder
  类实现了 gzip 的解码。

  _writer 属性的初始化是在读取完header 数据之后,见 GCDWebServerConnection类 _readRequestHeaders 方法。

  此处调动 performOpen: 是处理一些写入前的准备工作。
  */
  if (![_request performOpen:&error]) {
    GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
    [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
    return;
  }

  if (initialData.length) {
    //先将先前读取 header 时额外读取到的 body 数据"写"到 _request 中。
    if (![_request performWriteData:initialData error:&error]) {
      GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
      if (![_request performClose:&error]) {
        GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
      }
      [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
      return;
    }
    length -= initialData.length;
  }
  //如果 body 数据还有剩余
  if (length) {
    //读取剩余的 body 数据
    [self readBodyWithRemainingLength:length
                      completionBlock:^(BOOL success) {

                        NSError* localError = nil;
                        //处理一些"写"完后的清理工作
                        if ([_request performClose:&localError]) {
                          [self _startProcessingRequest];
                        } else {
                          GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
                          [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
                        }

                      }];
  } else {
    //处理一些"写"完后的清理工作
    if ([_request performClose:&error]) {
      [self _startProcessingRequest];
    } else {
      GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
      [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
    }
  }
}
读取剩余的 body 数据
- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block {
  GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]);
  NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity];
  //GCDWebServer 封装的从 connect socket 读取数据的方法
  [self readData:bodyData
           withLength:length
      completionBlock:^(BOOL success) {

        if (success) {
          if (bodyData.length <= length) {
            NSError* error = nil;
            //这里则会调用内部的 _writer 将读取到的 body 数据"写"到 _request中
            if ([_request performWriteData:bodyData error:&error]) {
              NSUInteger remainingLength = length - bodyData.length;
              //之所以可能会有遗留,是因为可能由于网络或其他原因,数据并未一次性的接收完毕
              if (remainingLength) {
                [self readBodyWithRemainingLength:remainingLength completionBlock:block];
              } else {
                block(YES);
              }
            } else {
              GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error);
              block(NO);
            }
          } else {
            GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", _socket);
            block(NO);
            GWS_DNOT_REACHED();
          }
        } else {
          block(NO);
        }

      }];
}
_readChunkedBodyWithInitialData:
- (void)_readChunkedBodyWithInitialData:(NSData*)initialData {
  NSError* error = nil;
  if (![_request performOpen:&error]) {
    GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error);
    [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
    return;
  }

  NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData];
  /* _readChunkedBodyWithInitialData: 只是个入口方法,readNextBodyChunk: 才是真正的通过 chunked 协议要求的格式读取body数据,
    这里就不做额外说明了 chunked 协议要求的格式规范可以参考 <<HTTP 权威指南>> 的 15.6 章节
  */
  [self readNextBodyChunk:chunkData
          completionBlock:^(BOOL success) {

            NSError* localError = nil;
            if ([_request performClose:&localError]) {
              [self _startProcessingRequest];
            } else {
              GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error);
              [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
            }

          }];
}

响应

在读取完body数据后,一个完整的http请求信息便生成了,请求信息会被封装在GCDWebServerRequest实例中传递给外部,外部获取到GCDWebServerRequest实例后进行处理,然后
返回一个GCDWebServerResponse实例,GCDWebServerResponse中封装了响应信息,同样的,GCDWebServerResponse也有若干个子类,以提供便捷的响应信息构造方法:
-GCDWebServerDataResponse: 通过传入原始的Data构造响应信息
-GCDWebServerErrorResponse: 提供快速构造出错响应信息的方法
-GCDWebServerFileResponse: 提供便捷的将文件内容作为响应信息的方式
-GCDWebServerStreamedResponse: 提供一种异步返回响应信息的方式

GCDWebServerResponse实例构造完后,接着便是将响应信息写入connect socket中,这些逻辑在GCDWebServerConnection_finishProcessingRequest中。具体的步骤如下:

第一步: 对比请求和响应信息中的缓存控制字段,以决定响应码是否是302,这段逻辑在 _CompareResources函数中,参与对
比的字段有Last-ModifiedETag

第二步: 设置响应信息中的header部分,设置的header字段有:
- Connection: 值设置为close
- Server: 值设置为服务器名称
- Date: 值设置为当前时间
- Last-Modified
- ETag
- Cache-Control
- Content-Type
- Content-Length
- Transfer-Encoding
- 其他的由 GCDWebServer 的使用者设置的额外头部字段

第三步: 往connect socket中写入http头部信息

第四步: 往connect socket中写入http body数据。GCDWebServerResponse 封装了响应信息,所以 GCDWebServerResponse 中拥有http body的数据,这一步要做的就是从 GCDWebServerResponse 实例中把body数据读取出来然后写入 connect socket 中,GCDWebServerResponse 有多个
子类,所以根据不同类的实例body数据的来源会不同:
- 如果是 GCDWebServerResponse 实例,那么body为空。
- 如果是 GCDWebServerDataResponse 实例,那么 body 数据则来自其内部的 _data 属性。
- 如果是 GCDWebServerErrorResponse 实例,由于其实继承于 GCDWebServerDataResponse ,所以 body 数据则来自其内部的 _data 属性。
- 如果是 GCDWebServerFileResponse 实例,那么 body 数据则来自其内部的 _file 属性所表示的文件中。
- 如果是 GCDWebServerStreamedResponse 实例,那么 body 数据则来自于外部提供的 GCDWebServerStreamBlock 执行后的结果。

body数据的来源已经确定了,但是这些数据不一定就是最终响应的http body数据,GCDWebServer 还需要根据设置决定是否需要对body数据
进行内容编码,目前 GCDWebServer 实现了的 gzip 编码。

以上步骤完成后,http body的数据就已经确定了,接着便是将 body 数据写入 connect socket 中,写入其实就是将body数据从服务端
传输到客户端的过程,此时会涉及到另外一个概念:http的传输编码(Transfer-Encoding)。

说明完处理请求及响应的逻辑后,下面讲一下代码中的实现:

处理完请求后,会调用 _finishProcessingRequest:方法
- (void)_finishProcessingRequest:(GCDWebServerResponse*)response {
  GWS_DCHECK(_responseMessage == NULL);
  BOOL hasBody = NO;

  if (response) {
    //这里处理了缓存控制字段的逻辑
    response = [self overrideResponse:response forRequest:_request];
  }
  if (response) {
    if ([response hasBody]) {
      //初始化 GCDWebServerResponse 内部的 _reader 属性(id <GCDWebServerBodyReader> 类型)
      [response prepareForReading];
      hasBody = !_virtualHEAD;
    }
    NSError* error = nil;
    // performOpen: 方法是用来完成 _reader 读取数据前的准备工作
    if (hasBody && ![response performOpen:&error]) {
      GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error);
    } else {
      _response = response;
    }
  }

  if (_response) {
    //初始化头部信息(状态码 等)
    [self _initializeResponseHeadersWithStatusCode:_response.statusCode];
    /*
        从 GCDWebServerResponse 实例中取出header信息,构造响应header数据。
    */
    if (_response.lastModifiedDate) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822((NSDate*)_response.lastModifiedDate));
    }
    if (_response.eTag) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag);
    }
    if ((_response.statusCode >= 200) && (_response.statusCode < 300)) {
      if (_response.cacheControlMaxAge > 0) {
        CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]);
      } else {
        CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache"));
      }
    }
    if (_response.contentType != nil) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType));
    }
    if (_response.contentLength != NSUIntegerMax) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]);
    }
    if (_response.usesChunkedTransferEncoding) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked"));
    }
    [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) {
      CFHTTPMessageSetHeaderFieldValue(_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
    }];
    //往 connect socket中写入头部数据
    [self writeHeadersWithCompletionBlock:^(BOOL success) {

      if (success) {
        if (hasBody) {
          //往 connect socket中写入body数据
          [self writeBodyWithCompletionBlock:^(BOOL successInner) {
            //处理 _reader 读取完后的清理工作
            [_response performClose];  // TODO: There's nothing we can do on failure as headers have already been sent

          }];
        }
      } else if (hasBody) {
        //处理 _reader 读取完后的清理工作
        [_response performClose];
      }

    }];
  } else {
    [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError];
  }
}
往 connect socket 中写入body数据
- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block {
  GWS_DCHECK([_response hasBody]);
  //从 _response 中读取出body数据
  [_response performReadDataWithCompletion:^(NSData* data, NSError* error) {

    if (data) {
      if (data.length) {
        //如果需要使用 chunked 传输编码(header 中 Transfer-Encoding: chunked)
        if (_response.usesChunkedTransferEncoding) {
          //以 chunked 要求的格式对 data 进行编码
          const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String];
          size_t hexLength = strlen(hexString);
          NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)];
          if (chunk == nil) {
            GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", _socket, error);
            block(NO);
            return;
          }
          char* ptr = (char*)[(NSMutableData*)chunk mutableBytes];
          bcopy(hexString, ptr, hexLength);
          ptr += hexLength;
          *ptr++ = '\r';
          *ptr++ = '\n';
          bcopy(data.bytes, ptr, data.length);
          ptr += data.length;
          *ptr++ = '\r';
          *ptr = '\n';
          data = chunk;
        }
        //写入至connect socket
        [self writeData:data
            withCompletionBlock:^(BOOL success) {

              if (success) {
                //只要当次读取有数据就会一直读,直到读不到数据后,添加 _lastChunkData
                [self writeBodyWithCompletionBlock:block];
              } else {
                block(NO);
              }

            }];
      } else {
        if (_response.usesChunkedTransferEncoding) {
          //在使用 chunked 传输编码情况下,尾部需要添加一个0字节chunk块
          [self writeData:_lastChunkData
              withCompletionBlock:^(BOOL success) {

                block(success);

              }];
        } else {
          block(YES);
        }
      }
    } else {
      GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", _socket, error);
      block(NO);
    }

  }];
}

写入完成后,服务端便会将连接关闭。(目前GCDWebServer并未实现Keep-Alive

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
flowable解读是指对flowable工作流引擎的代码进行分析和理解。flowable是一个开的工作流引擎,用于实现和管理复杂的业务流程。在解读过程中,可以深入了解flowable的设计原理、核心组件和算法实现。 根据引用,流程定义是将一个流程XML文件部署到flowable中,从而创建一个定义好的流程。因此,在flowable的解读中,可以关注流程定义的实现方式和流程XML文件的解析过程。 引用中提到了从基础讲起,结合应用场景,由浅入深地解释BPMN和Flowable的相关组件,并结合具体实例演示功能的使用和注意事项。这表明在解读中,可以结合实际应用场景,逐步深入地了解Flowable的不同组件的实现原理和使用方式。 引用中介绍了BPMN2.0定义的流程元素,包括流程对象、连接对象、泳道和数据和制品。在解读中,可以重点关注这些元素的实现和它们在flowable代码中的具体实现方式。 总而言之,flowable解读是通过对flowable工作流引擎的代码进行分析和理解,来深入了解flowable的设计原理、核心组件和算法实现。通过结合实际应用场景和流程定义的解析过程,我们能够更加全面地理解flowable工作流引擎的实现原理和使用方式。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Flowable从入门到码分析](https://blog.csdn.net/weixin_46399870/article/details/130277499)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Flowable从入门到精通码](https://download.csdn.net/download/qq_36305027/85422953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值