Skip to content

Commit

Permalink
Merge branch 'release/2.1.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
Kofktu committed Dec 29, 2020
2 parents 833db55 + c528db6 commit 09bb5c3
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 174 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.1
// swift-tools-version:5.3
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

[![Build Status](https://travis-ci.org/Kofktu/Sniffer.svg?branch=master)](https://travis-ci.org/Kofktu/Sniffer)
![Swift](https://img.shields.io/badge/Swift-5.0-orange.svg)
[![Platform](http://img.shields.io/cocoapods/p/SDWebImage.svg?style=flat)](http://cocoadocs.org/docsets/SDWebImage/)
[![CocoaPods](http://img.shields.io/cocoapods/v/Sniffer.svg?style=flat)](http://cocoapods.org/?q=name%3ASniffer%20author%3AKofktu)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-brightgreen.svg)](https://swift.org/package-manager/)

- Automatic networking activity logger
- intercepting any outgoing requests and incoming responses for debugging purposes.
Expand Down
2 changes: 1 addition & 1 deletion Sniffer.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pod::Spec.new do |s|
#

s.name = "Sniffer"
s.version = "2.1.1"
s.version = "2.1.2"
s.summary = "Automatic network activity logger for Swift"
s.homepage = "https://github.com/Kofktu/Sniffer"
# s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif"
Expand Down
10 changes: 8 additions & 2 deletions Sniffer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
C30E57021E541B9200B4C345 /* Sniffer.h in Headers */ = {isa = PBXBuildFile; fileRef = C30E56F41E541B9200B4C345 /* Sniffer.h */; settings = {ATTRIBUTES = (Public, ); }; };
C30E570D1E541BC900B4C345 /* Sniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30E570C1E541BC900B4C345 /* Sniffer.swift */; };
C30E570E1E5424BE00B4C345 /* Sniffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C30E570C1E541BC900B4C345 /* Sniffer.swift */; };
C3124464259AC583009A7ECD /* HTTPLogItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3124463259AC583009A7ECD /* HTTPLogItem.swift */; };
C356DDE51E555D6E00DF7467 /* BodyDeserializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C356DDE41E555D6E00DF7467 /* BodyDeserializer.swift */; };
C356DDE61E555FE500DF7467 /* BodyDeserializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C356DDE41E555D6E00DF7467 /* BodyDeserializer.swift */; };
C36AADDF259AD85C00B13544 /* HTTPLogItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3124463259AC583009A7ECD /* HTTPLogItem.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -36,6 +38,7 @@
C30E56FF1E541B9200B4C345 /* SnifferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnifferTests.swift; sourceTree = "<group>"; };
C30E57011E541B9200B4C345 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C30E570C1E541BC900B4C345 /* Sniffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sniffer.swift; sourceTree = "<group>"; };
C3124463259AC583009A7ECD /* HTTPLogItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPLogItem.swift; sourceTree = "<group>"; };
C356DDE41E555D6E00DF7467 /* BodyDeserializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BodyDeserializer.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -109,6 +112,7 @@
isa = PBXGroup;
children = (
C30E570C1E541BC900B4C345 /* Sniffer.swift */,
C3124463259AC583009A7ECD /* HTTPLogItem.swift */,
C356DDE41E555D6E00DF7467 /* BodyDeserializer.swift */,
);
path = Classes;
Expand Down Expand Up @@ -227,6 +231,7 @@
buildActionMask = 2147483647;
files = (
C356DDE51E555D6E00DF7467 /* BodyDeserializer.swift in Sources */,
C3124464259AC583009A7ECD /* HTTPLogItem.swift in Sources */,
C30E570D1E541BC900B4C345 /* Sniffer.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -236,6 +241,7 @@
buildActionMask = 2147483647;
files = (
C30E57001E541B9200B4C345 /* SnifferTests.swift in Sources */,
C36AADDF259AD85C00B13544 /* HTTPLogItem.swift in Sources */,
C30E570E1E5424BE00B4C345 /* Sniffer.swift in Sources */,
C356DDE61E555FE500DF7467 /* BodyDeserializer.swift in Sources */,
);
Expand Down Expand Up @@ -364,7 +370,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2.1.1;
MARKETING_VERSION = 2.1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.kofktu.Sniffer;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand All @@ -386,7 +392,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2.1.1;
MARKETING_VERSION = 2.1.2;
PRODUCT_BUNDLE_IDENTIFIER = com.kofktu.Sniffer;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down
213 changes: 213 additions & 0 deletions Sniffer/Classes/HTTPLogItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
//
// HTTPLogItem.swift
// Sniffer
//
// Created by Kofktu on 2020/12/29.
// Copyright © 2020 Kofktu. All rights reserved.
//

import Foundation

public final class HTTPLogItem {

var url: URL { urlRequest.url! }

private var urlRequest: URLRequest
private var urlResponse: URLResponse?
private var data: Data?
private var error: Error?

private var startDate: Date
private var duration: TimeInterval?

init(request: URLRequest) {
assert(request.url != nil)

self.urlRequest = request
startDate = Date()

logRequest()
}

func didReceive(response: URLResponse) {
self.urlResponse = response
data = Data()
}

func didReceive(data: Data) {
self.data?.append(data)
}

func didCompleteWithError(_ error: Error?) {
self.error = error
duration = fabs(startDate.timeIntervalSinceNow)

logDidComplete()
}

}

private extension HTTPLogItem {

private var logDivider: String {
return "==========================================================="
}

private var errorLogDivider: String {
return "===========================ERROR==========================="
}

func log(_ value: String, type: Sniffer.LogType) {
if let logger = Sniffer.onLogger {
logger(url, type, value)
} else {
print(value)
}
}

func logRequest() {
var result: [String] = [logDivider]

if let method = urlRequest.httpMethod {
result.append("Request [\(method)] : \(url)")
}

result.append(urlRequest.httpHeaderFieldsDescription)
result.append(urlRequest.bodyDescription)
result.append(logDivider)

log(result.filter { !$0.isEmpty }.joined(separator: "\n"), type: .request)
}

func logDidComplete() {
var result: [String] = [error != nil ? errorLogDivider : logDivider]
result.append("Response : \(url)")

if let duration = duration {
result.append("Duration: \(duration)s")
}

result.append(error != nil ? logErrorResponse() : logResponse())
result.append(logDivider)

log(result.filter { !$0.isEmpty }.joined(separator: "\n"), type: .response)
}

func logResponse() -> String {
guard let response = urlResponse else {
return ""
}

var result: [String] = []
var contentType = "application/octet-stream"

if let httpResponse = response as? HTTPURLResponse {
let localisedStatus = HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode).capitalized
result.append("Status: \(httpResponse.statusCode) - \(localisedStatus)")
result.append(httpResponse.httpHeaderFieldsDescription)

if let type = httpResponse.allHeaderFields["Content-Type"] as? String {
contentType = type
}
}

if let body = data,
let deserialize = Sniffer.find(deserialize: contentType)?.deserialize(body: body) ?? PlainTextBodyDeserializer().deserialize(body: body) {
result.append("Body: [")
result.append(deserialize)
result.append("]")
}

return result.filter { !$0.isEmpty }.joined(separator: "\n")
}

func logErrorResponse() -> String {
guard let error = error else {
return ""
}

var result: [String] = []

let nsError = error as NSError

result.append("Code : \(nsError.code)")
result.append("Description : \(nsError.localizedDescription)")

if let reason = nsError.localizedFailureReason {
result.append("Reason : \(reason)")
}
if let suggestion = nsError.localizedRecoverySuggestion {
result.append("Suggestion : \(suggestion)")
}

return result.filter { !$0.isEmpty }.joined(separator: "\n")
}

}

private extension URLRequest {

var httpHeaderFieldsDescription: String {
guard let headers = allHTTPHeaderFields, !headers.isEmpty else {
return ""
}

var values: [String] = []
values.append("Headers: [")
for (key, value) in headers {
values.append(" \(key) : \(value)")
}
values.append("]")
return values.joined(separator: "\n")
}

var bodyData: Data? {
httpBody ?? httpBodyStream.flatMap { stream in
let data = NSMutableData()
stream.open()
while stream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: 1024)
let length = stream.read(&buffer, maxLength: buffer.count)
data.append(buffer, length: length)
}
stream.close()
return data as Data
}
}

var bodyDescription: String {
guard let body = bodyData else {
return ""
}

let contentType = value(forHTTPHeaderField: "Content-Type") ?? "application/octet-stream"
var result: [String] = []

if let deserialized = Sniffer.find(deserialize: contentType)?.deserialize(body: body) {
result.append("Body: [")
result.append(deserialized)
result.append("]")
}

return result.filter { !$0.isEmpty }.joined(separator: "\n")
}

}

private extension HTTPURLResponse {

var httpHeaderFieldsDescription: String {
guard !allHeaderFields.isEmpty else {
return ""
}

var values: [String] = []
values.append("Headers: [")
for (key, value) in allHeaderFields {
values.append(" \(key) : \(value)")
}
values.append("]")
return values.joined(separator: "\n")
}

}
Loading

0 comments on commit 09bb5c3

Please sign in to comment.