Giter Site home page Giter Site logo

realhttp's Introduction

logo-library

Swift Platform Swift Package Manager CocoaPods Compatible

RealHTTP is a lightweight yet powerful async-based HTTP library made in Swift.
This project aims to make an easy-to-use, effortless HTTP client based on all the best new Swift features.

What will you get?

Below is a simple HTTP call in RealHTTP.

let todo = try await HTTPRequest("https://jsonplaceholder.typicode.com/todos/1")
           .fetch(Todo.self)

One line of code, including the automatic decode from JSON to object.

Of course, you can fully configure the request with many other parameters. Take a look here:

let req = HTTPRequest {
  // Setup default params
  $0.url = URL(string: "https://.../login")!
  $0.method = .post
  $0.timeout = 15

  // Setup some additional settings
  $0.redirectMode = redirect
  $0.maxRetries = 4
  $0.headers = HTTPHeaders([
    .init(name: .userAgent, value: myAgent),
    .init(name: "X-API-Experimental", value: "true")
  ])
   
  // Setup URL query params & body
  $0.addQueryParameter(name: "full", value: "1")
  $0.addQueryParameter(name: "autosignout", value: "30")
  $0.body = .json(["username": username, "pwd": pwd])
}
let _ = try await req.fetch()

The code is fully type-safe.

What about the stubber?

Integrated stubber is perfect to write your own test suite:

That's a simple stubber which return the original request as response:

let echoStub = HTTPStubRequest().match(urlRegex: "*").stubEcho()
HTTPStubber.shared.add(stub: echoStub)
HTTPStubber.shared.enable()

Of course you can fully configure your stub with rules (regex, URI template and more):

// This is a custom stubber for any post request.
var stub = HTTPStubRequest()
      .stub(for: .post, { response in
        response.responseDelay = 5
        response.headers = HTTPHeaders([
          .contentType: HTTPContentType.bmp.rawValue,
          .contentLength: String(fileSize,
        ])
        response.body = fileContent
      })
HTTPStubber.shared.add(stub: stub)

That's all!

Feature Highlights

RealHTTP offers lots of features and customization you can find in our extensive documentation and test suite.
Some of them are:

  • Async/Await native support
  • Requests queue built-in
  • Based upon native URLSession technology
  • Advanced retry mechanisms
  • Chainable & customizable response validators like Node's Express.js
  • Automatic Codable object encoding/decoding
  • Customizable decoding of objects

And for pro users:

  • Powerful integrated HTTP Stub for your mocks
  • Combine publisher adapter
  • URI templating system
  • Resumable download/uploads with progress tracking
  • Native Multipart Form Data support
  • Advanced URL connection metrics collector
  • SSL Pinning, Basic/Digest Auth
  • TSL Certificate & Public Key Pinning
  • cURL debugger

Documentation

RealHTTP provides an extensive documentation.

API Reference

RealHTTP is fully documented at source-code level. You'll get autocomplete with doc inside XCode for free; moreover you can read the full Apple's DoCC Documentation automatically generated thanks to Swift Package Index Project from here:

👉 API REFERENCE

Test Suite

RealHTTP has an extensive unit test suite covering many standard and edge cases, including request build, parameter encoding, queuing, and retries strategies.
See the XCTest suite inside Tests/RealHTTPTests folder.

Requirements

RealHTTP can be installed on any platform which supports:

  • iOS 13+, macOS Catalina+, watchOS 6+, tvOS 13+
  • Xcode 13.2+
  • Swift 5.5+

Installation

Swift Package Manager

Add it as a dependency in a Swift Package, and add it to your Package. Swift:

dependencies: [
  .package(url: "https://github.com/immobiliare/RealHTTP.git", from: "1.0.0")
]

And add it as a dependency of your target:

targets: [
  .target(name: "MyTarget", dependencies: [
    .product(name: "https://github.com/immobiliare/RealHTTP.git", package: "RealHTTP")
  ])
]

In Xcode 11+ you can also navigate to the File menu and choose Swift Packages -> Add Package Dependency..., then enter the repository URL and version details.

CocoaPods

RealHTTP can be installed with CocoaPods by adding pod 'RealHTTP' to your Podfile.

pod 'RealHTTP'

Powered Apps

The fantastic mobile team at ImmobiliareLabs created RealHTTP. We are currently using RealHTTP in all of our products.

If you are using RealHTTP in your app drop us a message.

Support & Contribute

Made with ❤️ by ImmobiliareLabs & Contributors

We'd love for you to contribute to RealHTTP!
If you have questions about using RealHTTP, bugs, and enhancement, please feel free to reach out by opening a GitHub Issue.

ImmobiliareLabs

realhttp's People

Contributors

erdemildiz-loodos avatar frajaona avatar jellybellydev avatar jgoodrick avatar kondamon avatar malcommac avatar piotrthoc avatar poissonballon avatar tciuro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

realhttp's Issues

Why custom validation errors are embedded in a HTTPError ?

I have a custom validator that parses the JSON response and tries to build an Error struct.
If it builds the error successfully, my validator will return with a .failChain(responseError), but in my catch at the call site, the error is still embedded into a HTTPError, which makes the catch a bit convoluted:

import RealHTTP
import MyService

do { 
  let result = try await service.callSomeEndpoint()
} catch let httpError as HTTPError {
  if let responseError = httpError.error as? ResponseError {
    // handle my custom response error here
  } else {
    // handle the http error?
  }
} catch {
  // 
}

Question: what's your rationale behind automatically nesting the validator custom errors in a HTTPError ?

I would probably prefer to have 2 dedicated catches:

} catch let responseError as ResponseError {
  ...
} catch let httpError as HTTPError {
  ...
}

So I can ignore the HTTPError and fallback to a generic catch if I'd like to.. and I could also avoid leaking the RealHTTP implementation/imports in all my services ;)

Wrong trigger condition in HTTPAltValidator

Bug Report

HTTPAltValidator triggeredCodes are not checked correctly and validator passes even when statuscode are triggered.

Q A
BC Break yes
Version 0.9.18 or previous

[Bug]: Use of unimplemented 'init(request:cachedResponse:client:)' in RealHTTP.HTTPStubURLProtocol

Platform Version

Any

SDK Version

Any

Xcode Version

Any

Steps To Reproduce

It's not always reproducible, but typically, it happens at the startup.

The error is:

RealHTTP/HTTPStubURLProtocol.swift:18: Fatal error: Use of unimplemented initializer 'init(request:cachedResponse:client:)' for class 'RealHTTP.HTTPStubURLProtocol'
(lldb) 

Expected Behavior

Init continue w/o crash

Actual Incorrect Behavior

Crash of the hosted app.

Cannot run example code

I tried your example , but I get error

Instance method 'fetch' requires that 'HTTPRequest' conform to 'APIResourceConvertible'"

That error appeared hear

public extension HTTPClient {
func fetch<T: APIResourceConvertible>(_ convertible: T) async throws -> T.Result {
let result = try await fetch(convertible.request())
return try result.decode(T.Result.self)
}
}

Crash initializing HTTPMetrics Stage object with invalid DateInterval

Bug Report

Sometimes the start and end data are received in the wrong order (end happens before start).
It usually happens with domainLookupStartDate (start) and responseEndDate objects.
While we've deeply investigated the issue no further evidence is found.
In order to fix the issue and prevents any crash, we will add a check upon these variables in order to fix their timing order.

image

Many waitForStatusRecordUnlock crash, thread lock

Bug Report

Q A
BC Break no
Version 1.6.2

Summary

We have many (but intermittent) waitForStatusRecordUnlock crash and we doesn't know why,

Do you have any idea ? Misuse of the HTTPClient ?

Thanks for ur helping,

Screenshot 2022-07-26 at 17 14 44

SPM Bug: Xcode 134.1 100 % CPU usage with SwiftLintPlugin enabled

Platform Version

Any

SDK Version

1.7.0

Xcode Version

13.4.1 (13F100)

Steps To Reproduce

Embed the latest 1.7.0 in any bigger project and run project.

Xcode has over 100% usage as soon as the play button is being hit. Also restart of Xcode with project or restart of Mac and opening the project leads to 100% CPU usage. Xcode is extremely slow and doesn't show errors in the code anymore.

Expected Behavior

Version not affecting Xcode CPU usage.

Actual Incorrect Behavior

100% CPU usage when SDK is embedded in project.

[Feature]: Cookies set in Set-Cookie header are now printed in `cURLDescription()` (along with any set in `NSHTTPCookieStorage`)

What problem are you facing?

Actually, we'll deliberately ignore all cookies set with the Set-Cookie header when printing the cURL description via RealHTTP. It's annoying; sometimes, it's helpful to set cookies in this manner instead of creating an NSHTTPCookie object.

In this task, we'll remove the where condition when printing header cookies.

    private static func addHeaders(for request: URLRequest, whenIn client: HTTPClient, into components: inout [String]) {
        let configuration = client.session.configuration
        var headers = HTTPHeaders()
        
        for header in configuration.headers where header.name != "Cookie" {
            headers[header.name] = header.value
        }
        
        for header in request.headers where header.name != "Cookie" {
            headers[header.name] = header.value
        }
        
        for header in headers {
            let escapedValue = header.value.replacingOccurrences(of: "\"", with: "\\\"")
            components.append("-H \"\(header.name.rawValue): \(escapedValue)\"")
        }
    }

Other Information

No response

[Bug]: Can't compile on VisionOS

Platform Version

VisionOS 1.0

SDK Version

1.8.3

Xcode Version

15.0 beta 8

Steps To Reproduce

Installed via Package Manager
Imported to project Frameworks...

Expected Behavior

Project compiles

Actual Incorrect Behavior

Project does not compile with errors:
Cannot find 'kUTTagClassFilenameExtension' in scope
Cannot find 'kUTTagClassMIMEType' in scope
Cannot find 'UTTypeCreatePreferredIdentifierForTag' in scope

if Foundation+Extensions.swift file from RealHTTP

HTTPDataLoader crashes decrementStrong on line 438

Bug Report

Q A
BC Break no
Version 1.5.2

Summary

Hello, I am experiencing some crash using RealHTTP on my app. Not sure if it comes from the lib or the use I do.

Current behavior

The app crash when deallocating a pointer on line 438 of the file HTTPDataLoader.swift.

How to reproduce

2% of my users are experiencing the crash but not sure of how to reproduce for the moment.

Here are the stack trace : crash.txt
Capture d’écran 2022-07-26 à 17 45 34

Thanks for your help

isAbsoluteURL regex doesn't work like expected

Bug Report

Q A
BC Break no
Version 1.52

Summary

isAbsoluteURL regex doesn't work like expected

Current behavior

Impossible to add urlComponents.queryItems coz path is absolute :/

How to reproduce

/connect/login is an absolute URL
connect/login is an absolute URL

Expected behavior

/connect/login is not an absolute URL
connect/login is not an absolute URL

I think error is the ? after the first group in the regex

SSL Pinning, URLAuthenticationChallenge and Auto-Accept-Self-Signed support for HTTPSecurity

Feature Request

Implement the following security options for HTTPSecurity both for HTTPRequest and globally for HTTPClient:

  • SSL Pinning: uses a list of SSL Certificates or certificates bundled inside an app sandboxed directory
  • Auto Accept Self Signed Certificates: support for auto accept self signed certificates for debug/testing purpose
  • Support for standard URLAuthenticationChallenge challange
Q A
New Feature yes
BC Break no

Explicitly specify boolEncoding and arrayEncoding strategies when adding parameters in bulk via formURLEncodedBody()

What problem are you facing?

I have a private method in my custom client to build my HTTPRequests as follow:

func buildRequest(
        path: String,
        method: HTTPMethod,
        parameters: HTTPRequestParametersDict?,
        timeout: TimeInterval?
    ) -> HTTPRequest {
        let request = HTTPRequest {
            $0.method = method
            $0.path = path
            $0.timeout = timeout

            if let parameters = parameters {
                $0.body = .formURLEncodedBody(defaultParameters.merging(parameters, uniquingKeysWith: { $1 }))
            } else {
                $0.body = .formURLEncodedBody(defaultParameters)
            }
        }
        return request
    }

The problem with this is that I couldn't find a way to specify the boolEncoding and arrayEncoding.

So my question is:
Is there a way to specify it without iterating over all the parameters and manually use the request.addParameter(..., boolEncoding:, arrayEncoding:)? Or do you seen it as a new feature, like specifying it in the .formURLEncodedBody() method ?

Other Information

No response

Crash in HTTPDataLoader.fetch

Hi, I get some rare crash report in Sentry that lead me to an issue in HTTPDataLoader.
I don't know how to reproduce them and I am not sure what's going on.
Note: our app is using the 1.8.1 version of RealHTTP.

Here is the full crash report:

OS Version: iOS 15.4 (19E241)
Report Version: 104

Exception Type: EXC_CRASH (SIGABRT)
Crashed Thread: 3

Application Specific Information:
Metadata allocator corruption: allocation is NULL. curState: {0x0, 4248} - curStateReRead: {0x0, 4248} - newState: {0x20, 4216} - allocatedNewPage: false - requested size: 32 - sizeWithHeader: 32 - alignment: 8 - Tag: 14


Thread 3 Crashed:
0   libsystem_kernel.dylib          0x376432bbc         __pthread_kill
1   libsystem_pthread.dylib         0x3b7795850         pthread_kill
2   libsystem_c.dylib               0x3163036a0         abort
3   libswiftCore.dylib              0x30a6c5018         swift::fatalError
4   libswiftCore.dylib              0x30a6ce694         swift::MetadataAllocator::Allocate
5   libswiftCore.dylib              0x30a6cf1f0         _swift_getGenericMetadata
6   libswiftCore.dylib              0x30a6b2d64         __swift_instantiateCanonicalPrespecializedGenericMetadata
7   libswiftCore.dylib              0x30a6ef59c         swift::Demangle::__runtime::TypeDecoder<T>::decodeMangledType
8   libswiftCore.dylib              0x30a6ea020         swift_getTypeByMangledNodeImpl
9   libswiftCore.dylib              0x30a6e9d98         swift_getTypeByMangledNode
10  libswiftCore.dylib              0x30a6ea50c         swift_getTypeByMangledNameImpl
11  libswiftCore.dylib              0x30a6e7d10         swift_getTypeByMangledName
12  libswiftCore.dylib              0x30a6e7f24         swift_getTypeByMangledNameInContext
13  ZVVOneApp                       0x20093609c         __swift_instantiateConcreteTypeFromMangledName
14  ZVVOneApp                       0x200954ae4         ThreadSafeDictionary.subscript.setter (ThreadSafeDictionary.swift:66)
15  ZVVOneApp                       0x200952c38         HTTPDataLoader.fetch (HTTPDataLoader.swift:228)
16  ZVVOneApp                       0x20099017c         thunk for closure
17  Foundation                      0x3042e2268         __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__
18  Foundation                      0x3042f391c         -[NSBlockOperation main]
19  Foundation                      0x3042cda88         __NSOPERATION_IS_INVOKING_MAIN__
20  Foundation                      0x3042ddf1c         -[NSOperation start]
21  Foundation                      0x3042e1364         __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
22  Foundation                      0x3042eeb78         __NSOQSchedule_f
23  libdispatch.dylib               0x300e9a044         _dispatch_block_async_invoke2
24  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
25  libdispatch.dylib               0x300e8fab4         _dispatch_continuation_pop$VARIANT$armv81
26  libdispatch.dylib               0x300e8f1f0         _dispatch_async_redirect_invoke
27  libdispatch.dylib               0x300e9c62c         _dispatch_root_queue_drain
28  libdispatch.dylib               0x300e9cde8         _dispatch_worker_thread2
29  libsystem_pthread.dylib         0x3b7789dd0         _pthread_wqthread

Thread 0
0   libswiftCore.dylib              0x30a6d6f78         swift::_getWitnessTable
1   SwiftUI                         0x31025345c         PhysicalButtonPressBehavior.body.getter
2   SwiftUI                         0x31019ad3c         ViewBodyAccessor.updateBody
3   SwiftUI                         0x310185bd0         BodyAccessor.setBody
4   SwiftUI                         0x3101712bc         ViewBodyAccessor.updateBody
5   SwiftUI                         0x310179514         StaticBody.updateValue
6   SwiftUI                         0x3101cfe38         Attribute.init<T>
7   AttributeGraph                  0x36bf1c110         AG::Graph::UpdateStack::update
8   AttributeGraph                  0x36bf1b87c         AG::Graph::update_attribute
9   AttributeGraph                  0x36bf1a790         AG::Subgraph::update
10  SwiftUI                         0x3101598e0         GraphHost.flushTransactions
11  SwiftUI                         0x310c1463c         GraphHost.asyncTransaction<T>
12  SwiftUI                         0x31014ce50         ViewGraphDelegate.updateGraph<T>
13  SwiftUI                         0x310157218         ViewRendererHost.updateViewGraph<T>
14  SwiftUI                         0x310150f6c         ViewRendererHost.updateViewGraph<T>
15  SwiftUI                         0x31014ab80         ViewGraphDelegate.updateGraph<T>
16  SwiftUI                         0x310149e60         GraphHost.init
17  SwiftUI                         0x31023245c         thunk for closure
18  SwiftUI                         0x310148650         NSRunLoop.flushObservers
19  SwiftUI                         0x3101486c8         NSRunLoop.addObserver
20  SwiftUI                         0x310217e54         thunk for closure
21  libswiftObjectiveC.dylib        0x365216bcc         autoreleasepool<T>
22  SwiftUI                         0x31014857c         NSRunLoop.addObserver
23  SwiftUI                         0x3101487c0         NSRunLoop.addObserver
24  CoreFoundation                  0x3014b4218         __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
25  CoreFoundation                  0x301485450         __CFRunLoopDoObservers
26  CoreFoundation                  0x3014809d8         __CFRunLoopRun
27  CoreFoundation                  0x301493c2c         CFRunLoopRunSpecific
28  GraphicsServices                0x3428e5984         GSEventRunModal
29  UIKitCore                       0x305fc1c4c         -[UIApplication _run]
30  UIKitCore                       0x305d5b3cc         UIApplicationMain
31  SwiftUI                         0x310335324         KitRendererCommon
32  SwiftUI                         0x31027fdf8         runApp<T>
33  SwiftUI                         0x310265798         App.main
34  ZVVOneApp                       0x200091a48         [inlined] ZVVApp.$main (ZVVApp.swift:5)
35  ZVVOneApp                       0x200091a48         main
36  <unknown>                       0x1048203d0         <redacted>

Thread 1
0   libdispatch.dylib               0x300eb6aa0         dispatch_queue_attr_make_with_autorelease_frequency
1   CFNetwork                       0x302465cd8         CFURLRequestSetShouldStartSynchronously
2   CFNetwork                       0x30246ec84         CFURLRequestSetHTTPRequestBodyParts
3   CFNetwork                       0x30249f6a0         CFURLConnectionCreateWithProperties
4   CFNetwork                       0x30251ba0c         CFURLDownloadCancel
5   CFNetwork                       0x302443d80         CFURLRequestSetURL
6   CFNetwork                       0x302438514         _CFNetworkErrorGetLocalizedRecoverySuggestion
7   CFNetwork                       0x30247869c         CFURLCacheMemoryCapacity
8   CFNetwork                       0x302456068         CFURLRequestSetHTTPRequestMethod
9   CFNetwork                       0x30244ed44         _CFURLRequestSetContentDispositionEncodingFallbackArray
10  CFNetwork                       0x30248fbdc         CFHTTPCookieStorageCreateFromFile
11  CFNetwork                       0x302444de4         CFURLRequestSetURL
12  CFNetwork                       0x302446aec         CFHTTPMessageCopyBody
13  libdispatch.dylib               0x300eb7090         _dispatch_call_block_and_release
14  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
15  libdispatch.dylib               0x300e934a0         _dispatch_lane_serial_drain$VARIANT$armv81
16  libdispatch.dylib               0x300e93f74         _dispatch_lane_invoke$VARIANT$armv81
17  libdispatch.dylib               0x300e9d8dc         _dispatch_workloop_worker_thread
18  libsystem_pthread.dylib         0x3b7789e0c         _pthread_wqthread

Thread 2
0   libsystem_kernel.dylib          0x37642d688         __ulock_wait
1   libsystem_platform.dylib        0x3b7776120         _os_unfair_lock_lock_slow
2   libswiftCore.dylib              0x30a6cf064         _swift_getGenericMetadata
3   libswiftCore.dylib              0x30a6b2d64         __swift_instantiateCanonicalPrespecializedGenericMetadata
4   libswiftCore.dylib              0x30a6ef59c         swift::Demangle::__runtime::TypeDecoder<T>::decodeMangledType
5   libswiftCore.dylib              0x30a6ea020         swift_getTypeByMangledNodeImpl
6   libswiftCore.dylib              0x30a6e9d98         swift_getTypeByMangledNode
7   libswiftCore.dylib              0x30a6ea50c         swift_getTypeByMangledNameImpl
8   libswiftCore.dylib              0x30a6e7d10         swift_getTypeByMangledName
9   libswiftCore.dylib              0x30a6e7f24         swift_getTypeByMangledNameInContext
10  ZVVOneApp                       0x20093609c         __swift_instantiateConcreteTypeFromMangledName
11  ZVVOneApp                       0x200954ae4         ThreadSafeDictionary.subscript.setter (ThreadSafeDictionary.swift:66)
12  ZVVOneApp                       0x200952c38         HTTPDataLoader.fetch (HTTPDataLoader.swift:228)
13  ZVVOneApp                       0x20099017c         thunk for closure
14  Foundation                      0x3042e2268         __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__
15  Foundation                      0x3042f391c         -[NSBlockOperation main]
16  Foundation                      0x3042cda88         __NSOPERATION_IS_INVOKING_MAIN__
17  Foundation                      0x3042ddf1c         -[NSOperation start]
18  Foundation                      0x3042e1364         __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
19  Foundation                      0x3042eeb78         __NSOQSchedule_f
20  libdispatch.dylib               0x300e9a044         _dispatch_block_async_invoke2
21  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
22  libdispatch.dylib               0x300e8fab4         _dispatch_continuation_pop$VARIANT$armv81
23  libdispatch.dylib               0x300e8f1f0         _dispatch_async_redirect_invoke
24  libdispatch.dylib               0x300e9c62c         _dispatch_root_queue_drain
25  libdispatch.dylib               0x300e9cde8         _dispatch_worker_thread2
26  libsystem_pthread.dylib         0x3b7789dd0         _pthread_wqthread

Thread 3 Crashed:
0   libsystem_kernel.dylib          0x376432bbc         __pthread_kill
1   libsystem_pthread.dylib         0x3b7795850         pthread_kill
2   libsystem_c.dylib               0x3163036a0         abort
3   libswiftCore.dylib              0x30a6c5018         swift::fatalError
4   libswiftCore.dylib              0x30a6ce694         swift::MetadataAllocator::Allocate
5   libswiftCore.dylib              0x30a6cf1f0         _swift_getGenericMetadata
6   libswiftCore.dylib              0x30a6b2d64         __swift_instantiateCanonicalPrespecializedGenericMetadata
7   libswiftCore.dylib              0x30a6ef59c         swift::Demangle::__runtime::TypeDecoder<T>::decodeMangledType
8   libswiftCore.dylib              0x30a6ea020         swift_getTypeByMangledNodeImpl
9   libswiftCore.dylib              0x30a6e9d98         swift_getTypeByMangledNode
10  libswiftCore.dylib              0x30a6ea50c         swift_getTypeByMangledNameImpl
11  libswiftCore.dylib              0x30a6e7d10         swift_getTypeByMangledName
12  libswiftCore.dylib              0x30a6e7f24         swift_getTypeByMangledNameInContext
13  ZVVOneApp                       0x20093609c         __swift_instantiateConcreteTypeFromMangledName
14  ZVVOneApp                       0x200954ae4         ThreadSafeDictionary.subscript.setter (ThreadSafeDictionary.swift:66)
15  ZVVOneApp                       0x200952c38         HTTPDataLoader.fetch (HTTPDataLoader.swift:228)
16  ZVVOneApp                       0x20099017c         thunk for closure
17  Foundation                      0x3042e2268         __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__
18  Foundation                      0x3042f391c         -[NSBlockOperation main]
19  Foundation                      0x3042cda88         __NSOPERATION_IS_INVOKING_MAIN__
20  Foundation                      0x3042ddf1c         -[NSOperation start]
21  Foundation                      0x3042e1364         __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
22  Foundation                      0x3042eeb78         __NSOQSchedule_f
23  libdispatch.dylib               0x300e9a044         _dispatch_block_async_invoke2
24  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
25  libdispatch.dylib               0x300e8fab4         _dispatch_continuation_pop$VARIANT$armv81
26  libdispatch.dylib               0x300e8f1f0         _dispatch_async_redirect_invoke
27  libdispatch.dylib               0x300e9c62c         _dispatch_root_queue_drain
28  libdispatch.dylib               0x300e9cde8         _dispatch_worker_thread2
29  libsystem_pthread.dylib         0x3b7789dd0         _pthread_wqthread

Thread 4
0   libsystem_pthread.dylib         0x3b7789934         start_wqthread

Thread 5 name: com.apple.uikit.eventfetch-thread
0   libsystem_kernel.dylib          0x37642caac         mach_msg_trap
1   libsystem_kernel.dylib          0x37642d078         mach_msg
2   CoreFoundation                  0x30147c764         __CFRunLoopServiceMachPort
3   CoreFoundation                  0x301480a6c         __CFRunLoopRun
4   CoreFoundation                  0x301493c2c         CFRunLoopRunSpecific
5   Foundation                      0x3042abea8         -[NSRunLoop(NSRunLoop) runMode:beforeDate:]
6   Foundation                      0x3042eae8c         -[NSRunLoop(NSRunLoop) runUntilDate:]
7   UIKitCore                       0x305f4109c         -[UIEventFetcher threadMain]
8   Foundation                      0x3042f8d28         __NSThread__start__
9   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 6
0   libsystem_kernel.dylib          0x37642d688         __ulock_wait
1   libsystem_platform.dylib        0x3b7776120         _os_unfair_lock_lock_slow
2   libswiftCore.dylib              0x30a6cf064         _swift_getGenericMetadata
3   libswiftCore.dylib              0x30a6b2d64         __swift_instantiateCanonicalPrespecializedGenericMetadata
4   libswiftCore.dylib              0x30a6ef59c         swift::Demangle::__runtime::TypeDecoder<T>::decodeMangledType
5   libswiftCore.dylib              0x30a6ea020         swift_getTypeByMangledNodeImpl
6   libswiftCore.dylib              0x30a6e9d98         swift_getTypeByMangledNode
7   libswiftCore.dylib              0x30a6ea50c         swift_getTypeByMangledNameImpl
8   libswiftCore.dylib              0x30a6e7d10         swift_getTypeByMangledName
9   libswiftCore.dylib              0x30a6e7f24         swift_getTypeByMangledNameInContext
10  ZVVOneApp                       0x20093609c         __swift_instantiateConcreteTypeFromMangledName
11  ZVVOneApp                       0x200954ae4         ThreadSafeDictionary.subscript.setter (ThreadSafeDictionary.swift:66)
12  ZVVOneApp                       0x200952c38         HTTPDataLoader.fetch (HTTPDataLoader.swift:228)
13  ZVVOneApp                       0x20099017c         thunk for closure
14  Foundation                      0x3042e2268         __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__
15  Foundation                      0x3042f391c         -[NSBlockOperation main]
16  Foundation                      0x3042cda88         __NSOPERATION_IS_INVOKING_MAIN__
17  Foundation                      0x3042ddf1c         -[NSOperation start]
18  Foundation                      0x3042e1364         __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
19  Foundation                      0x3042eeb78         __NSOQSchedule_f
20  libdispatch.dylib               0x300e9a044         _dispatch_block_async_invoke2
21  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
22  libdispatch.dylib               0x300e8fab4         _dispatch_continuation_pop$VARIANT$armv81
23  libdispatch.dylib               0x300e8f1f0         _dispatch_async_redirect_invoke
24  libdispatch.dylib               0x300e9c62c         _dispatch_root_queue_drain
25  libdispatch.dylib               0x300e9cde8         _dispatch_worker_thread2
26  libsystem_pthread.dylib         0x3b7789dd0         _pthread_wqthread

Thread 7
0   libobjc.A.dylib                 0x3303ead58         objc_release
1   AppSSOCore                      0x3575717a0         -[SOConfigurationClient willHandleURL:responseCode:callerBundleIdentifier:]
2   AppSSOCore                      0x3575723f8         +[SOAuthorizationCore _canPerformAuthorizationWithURL:responseCode:callerBundleIdentifier:useInternalExtensions:]
3   CFNetwork                       0x30244d964         CFURLRequestSetMainDocumentURL
4   CFNetwork                       0x302444240         CFURLRequestSetURL
5   CFNetwork                       0x302454a78         CFURLRequestSetHTTPRequestMethod
6   CFNetwork                       0x30244cbcc         CFURLRequestSetMainDocumentURL
7   CFNetwork                       0x30244ed28         _CFURLRequestSetContentDispositionEncodingFallbackArray
8   CFNetwork                       0x30248fbdc         CFHTTPCookieStorageCreateFromFile
9   CFNetwork                       0x302444de4         CFURLRequestSetURL
10  CFNetwork                       0x302446aec         CFHTTPMessageCopyBody
11  libdispatch.dylib               0x300eb7090         _dispatch_call_block_and_release
12  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
13  libdispatch.dylib               0x300e934a0         _dispatch_lane_serial_drain$VARIANT$armv81
14  libdispatch.dylib               0x300e93f74         _dispatch_lane_invoke$VARIANT$armv81
15  libdispatch.dylib               0x300e9d8dc         _dispatch_workloop_worker_thread
16  libsystem_pthread.dylib         0x3b7789e0c         _pthread_wqthread

Thread 8
0   libsystem_kernel.dylib          0x37642d484         __psynch_cvwait
1   libsystem_pthread.dylib         0x3b778a85c         _pthread_cond_wait$VARIANT$armv81
2   libc++.1.dylib                  0x330534898         std::__1::condition_variable::wait
3   NewRelic                        0x1055b0dbc         NewRelic::WorkQueue::task_thread
4   NewRelic                        0x1055b1340         std::__1::__async_assoc_state<T>::__execute
5   NewRelic                        0x1055b14e0         std::__1::__thread_proxy<T>
6   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 9
0   libsystem_kernel.dylib          0x37642d484         __psynch_cvwait
1   libsystem_pthread.dylib         0x3b778a85c         _pthread_cond_wait$VARIANT$armv81
2   libc++.1.dylib                  0x330534898         std::__1::condition_variable::wait
3   NewRelic                        0x1055b0dbc         NewRelic::WorkQueue::task_thread
4   NewRelic                        0x1055b1340         std::__1::__async_assoc_state<T>::__execute
5   NewRelic                        0x1055b14e0         std::__1::__thread_proxy<T>
6   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 10
0   libswiftCore.dylib              0x30a6e773c         swift::_gatherGenericParameterCounts
1   libswiftCore.dylib              0x30a6f19c8         _gatherGenericParameters
2   libswiftCore.dylib              0x30a6f0c38         (anonymous namespace)::DecodedMetadataBuilder::createBoundGenericType
3   libswiftCore.dylib              0x30a6eeec8         swift::Demangle::__runtime::TypeDecoder<T>::decodeMangledType
4   libswiftCore.dylib              0x30a6eaee0         swift::Demangle::__runtime::TypeDecoder<T>::decodeMangledType
5   libswiftCore.dylib              0x30a6ea020         swift_getTypeByMangledNodeImpl
6   libswiftCore.dylib              0x30a6e9d98         swift_getTypeByMangledNode
7   libswiftCore.dylib              0x30a6ea50c         swift_getTypeByMangledNameImpl
8   libswiftCore.dylib              0x30a6e7d10         swift_getTypeByMangledName
9   libswiftCore.dylib              0x30a6e7f24         swift_getTypeByMangledNameInContext
10  AttributeGraph                  0x36bf2109c         AG::swift::metadata::mangled_type_name_ref
11  AttributeGraph                  0x36bf21ea0         AG::LayoutDescriptor::Builder::visit_case
12  AttributeGraph                  0x36bf20798         AG::swift::metadata::visit
13  AttributeGraph                  0x36bf1ee70         AG::LayoutDescriptor::Builder::visit_element
14  AttributeGraph                  0x36bf21c14         AG::swift::metadata_visitor::visit_field
15  AttributeGraph                  0x36bf2082c         AG::swift::metadata::visit
16  AttributeGraph                  0x36bf1ee70         AG::LayoutDescriptor::Builder::visit_element
17  AttributeGraph                  0x36bf21c14         AG::swift::metadata_visitor::visit_field
18  AttributeGraph                  0x36bf2082c         AG::swift::metadata::visit
19  AttributeGraph                  0x36bf1ee70         AG::LayoutDescriptor::Builder::visit_element
20  AttributeGraph                  0x36bf24b7c         AG::swift::metadata::visit_heap_locals
21  AttributeGraph                  0x36bf20b20         AG::LayoutDescriptor::make_layout
22  AttributeGraph                  0x36bf21ccc         AG::(anonymous namespace)::LayoutCache::drain_queue
23  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
24  libdispatch.dylib               0x300e9c748         _dispatch_root_queue_drain
25  libdispatch.dylib               0x300e9cde8         _dispatch_worker_thread2
26  libsystem_pthread.dylib         0x3b7789dd0         _pthread_wqthread

Thread 11
0   libsystem_kernel.dylib          0x37642cfd0         __semwait_signal
1   libsystem_c.dylib               0x3162e8a34         nanosleep
2   libsystem_c.dylib               0x3162fc704         sleep
3   ZVVOneApp                       0x2008b7920         monitorCachedData (SentryCrashCachedData.c:144)
4   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 12
0   libsystem_kernel.dylib          0x37642caac         mach_msg_trap
1   libsystem_kernel.dylib          0x37642d078         mach_msg
2   libsystem_kernel.dylib          0x37643880c         thread_suspend
3   ZVVOneApp                       0x2008b31dc         handleExceptions (SentryCrashMonitor_MachException.c:295)
4   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 13
0   libsystem_kernel.dylib          0x37642caac         mach_msg_trap
1   libsystem_kernel.dylib          0x37642d078         mach_msg
2   ZVVOneApp                       0x2008b3208         handleExceptions (SentryCrashMonitor_MachException.c:303)
3   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 14
0   libsystem_kernel.dylib          0x37642cfd0         __semwait_signal
1   libsystem_c.dylib               0x3162e8a34         nanosleep
2   Foundation                      0x30430a9fc         +[NSThread sleepForTimeInterval:]
3   ZVVOneApp                       0x2008595ac         -[SentryANRTracker detectANRs] (SentryANRTracker.m:75)
4   Foundation                      0x3042f8d28         __NSThread__start__
5   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 15
0   libsystem_kernel.dylib          0x37642d484         __psynch_cvwait
1   libsystem_pthread.dylib         0x3b778a85c         _pthread_cond_wait$VARIANT$armv81
2   libc++.1.dylib                  0x330534898         std::__1::condition_variable::wait
3   NewRelic                        0x1055b0dbc         NewRelic::WorkQueue::task_thread
4   NewRelic                        0x1055b1340         std::__1::__async_assoc_state<T>::__execute
5   NewRelic                        0x1055b14e0         std::__1::__thread_proxy<T>
6   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 16
0   libsystem_kernel.dylib          0x37642caac         mach_msg_trap
1   libsystem_kernel.dylib          0x37642d078         mach_msg
2   CoreFoundation                  0x30147c764         __CFRunLoopServiceMachPort
3   CoreFoundation                  0x301480a6c         __CFRunLoopRun
4   CoreFoundation                  0x301493c2c         CFRunLoopRunSpecific
5   Foundation                      0x3042abea8         -[NSRunLoop(NSRunLoop) runMode:beforeDate:]
6   Foundation                      0x3042ac61c         -[NSRunLoop(NSRunLoop) run]
7   SwiftUI                         0x3101ea5bc         DisplayLink.asyncThread
8   SwiftUI                         0x3101e8860         DisplayLink.asyncThread
9   Foundation                      0x3042f8d28         __NSThread__start__
10  libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 17
0   libsystem_kernel.dylib          0x37642caac         mach_msg_trap
1   libsystem_kernel.dylib          0x37642d078         mach_msg
2   CoreFoundation                  0x30147c764         __CFRunLoopServiceMachPort
3   CoreFoundation                  0x301480a6c         __CFRunLoopRun
4   CoreFoundation                  0x301493c2c         CFRunLoopRunSpecific
5   CFNetwork                       0x302673dc8         _CFURLStorageSessionDisableCache
6   Foundation                      0x3042f8d28         __NSThread__start__
7   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 18
0   libsystem_kernel.dylib          0x37642caac         mach_msg_trap
1   libsystem_kernel.dylib          0x37642d078         mach_msg
2   CoreFoundation                  0x30147c764         __CFRunLoopServiceMachPort
3   CoreFoundation                  0x301480a6c         __CFRunLoopRun
4   CoreFoundation                  0x301493c2c         CFRunLoopRunSpecific
5   CoreFoundation                  0x30150edc8         CFRunLoopRun
6   CoreMotion                      0x31a3669dc         CLMotionActivity::isTypeInVehicle
7   libsystem_pthread.dylib         0x3b778b344         _pthread_start

Thread 19
0   libsystem_kernel.dylib          0x37642d688         __ulock_wait
1   libsystem_platform.dylib        0x3b7776120         _os_unfair_lock_lock_slow
2   libswiftCore.dylib              0x30a6cf064         _swift_getGenericMetadata
3   libswiftCore.dylib              0x30a6b2d64         __swift_instantiateCanonicalPrespecializedGenericMetadata
4   libswiftCore.dylib              0x30a6ef59c         swift::Demangle::__runtime::TypeDecoder<T>::decodeMangledType
5   libswiftCore.dylib              0x30a6ea020         swift_getTypeByMangledNodeImpl
6   libswiftCore.dylib              0x30a6e9d98         swift_getTypeByMangledNode
7   libswiftCore.dylib              0x30a6ea50c         swift_getTypeByMangledNameImpl
8   libswiftCore.dylib              0x30a6e7d10         swift_getTypeByMangledName
9   libswiftCore.dylib              0x30a6e7f24         swift_getTypeByMangledNameInContext
10  ZVVOneApp                       0x20093609c         __swift_instantiateConcreteTypeFromMangledName
11  ZVVOneApp                       0x200954ae4         ThreadSafeDictionary.subscript.setter (ThreadSafeDictionary.swift:66)
12  ZVVOneApp                       0x200952c38         HTTPDataLoader.fetch (HTTPDataLoader.swift:228)
13  ZVVOneApp                       0x20099017c         thunk for closure
14  Foundation                      0x3042e2268         __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__
15  Foundation                      0x3042f391c         -[NSBlockOperation main]
16  Foundation                      0x3042cda88         __NSOPERATION_IS_INVOKING_MAIN__
17  Foundation                      0x3042ddf1c         -[NSOperation start]
18  Foundation                      0x3042e1364         __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
19  Foundation                      0x3042eeb78         __NSOQSchedule_f
20  libdispatch.dylib               0x300e9a044         _dispatch_block_async_invoke2
21  libdispatch.dylib               0x300eb8090         _dispatch_client_callout
22  libdispatch.dylib               0x300e8fab4         _dispatch_continuation_pop$VARIANT$armv81
23  libdispatch.dylib               0x300e8f1f0         _dispatch_async_redirect_invoke
24  libdispatch.dylib               0x300e9c62c         _dispatch_root_queue_drain
25  libdispatch.dylib               0x300e9cde8         _dispatch_worker_thread2
26  libsystem_pthread.dylib         0x3b7789dd0         _pthread_wqthread

Custom error is returned from custom validator and/or decode() function

I have a custom validator like this:

public class ErrorMessageValidator: HTTPValidator {

    public func validate(response: HTTPResponse, forRequest request: HTTPRequest) -> HTTPResponseValidatorResult {
        if [HTTPStatusCode.ResponseType.success, .redirection].contains(response.statusCode.responseType) {
            return .nextValidator
        }

        guard let data = response.data else {
            return .failChain(HTTPError(.invalidResponse))
        }

        let jsonData = JSON(data)

        guard let message = jsonData.dictionary?["message"]?.string else {
            return .nextValidator
        }

        return .failChain(MyError(title: message))
    }
}

And I get to the last line as expected, so a MyError is returned, wrapped into the failChain.

But in my calling code:

let request = myServerCall(myParameters).httpRequest()
let response = try await request.fetch(MyModel.self)

an error is thrown (expected) but it is an HTTPError with an empty error property, so my MyError isn't available here

Use HTTPStubber with iOS < 13 ?

Hi,
I am using HTTPStubber and love its simplicity and power.

I was wondering what would be the best approach to use it in a project with iOS 11+ support ?
Since I can't use async/await, is there any way I can still use it? Or should I start looking at a custom implementation (or revert back to the old OHHTTPStubs 😒)?

Any thoughts?

Modify the body of a request after initialization before executing it

Hi! That's an awesome library :)
I am trying to find a replacement to my verbose Moya/Alamofire layer and I came across RealHTTP that works pretty well!
I am just trying to see what would be the best approach to intercept a request and mutate the request body. For example, I'd like to send URL encoded forms, and have an interceptor that would add some extra data on top on the initial ones.

Since req.body is a Data, the req.urlRequestModifier is not very helpful in this case :(
Any idea ?
Or should I resort to defer the creation of the HttpRequest at the very last step and pass a Dictionary around ?

Ability to return a custom HTTPResponse as result of a validation inside the HTTPValidator response

Feature Request

HTTPValidator responses include the ability to retry a call, fail the chain or move successfully to the next validator.
Sometimes you may want to slightly modify the received response by altering some values or return a custom subclass of the HTTPResponse with some data inside.

This involves some changes:

  • The HTTPResponse struct becomes a class so we can inherit from it
  • Some properties of the HTTPResponse must be marked as open
  • HTTPValidator allows a new return value of HTTPResponseValidatorResult called nextValidatorWithResponse which allows to return a new HTTPResponse instance which is forwarded along any subsequent validator until the end.

The important change is the transformation to struct which implies changes from the memory management side.

[Bug]: HTTP Stubbing: URL matcher incorrectly strips port when using "ignore query parameters" option

Platform Version

iOS 16.1

SDK Version

1.4.0

Xcode Version

Xcode 14.1

Steps To Reproduce

Run the following code:

let stub = HTTPStubRequest()
  match(URL: URL(string: "http://localhost:3001/some/path")!)
  .stub(for: .get) { _, _ in
    let response = HTTPStubResponse()
    response.statusCode = .ok
    return response
}
        
let matches = stub.matchers[0].matches(request: URLRequest(url: URL(string: "http://localhost:3001/some/path")!), for: stub)

Note that matches should be true, but it is false.

This is because the .ignoreQueryParameters option results in the port getting stripped from the URL. The code that does that is in Foundation+Extension.swift:

// Returns the base URL string build with the scheme, host and path.
/// For example:
/// "https://www.apple.com/v1/test?param=test"
/// would be "https://www.apple.com/v1/test"
public var baseString: String? {
  guard let scheme = scheme, let host = host else { return nil }
  return scheme + "://" + host + path
}

This strips the query parameters as expected, but host doesn't include the port, so the resulting URL is also stripped of the port.

The above example returns true for matches if the ports are removed from the URLs.

Expected Behavior

URL matching should work as expected for URLs with manually specified ports, when the .ignoreQueryParameters option is enabled.

Actual Incorrect Behavior

URLs fail to match even if they are exactly the same if .ignoreQueryParameters is enabled and the URLs contain explicit ports.

Crash in HTTPMetrics : Attempted to dereference garbage pointer

Bug Report

Q A
BC Break no
Version 1.5.2

Summary

It seems that there is still something going on with HTTPMetrics in the 1.5.2 version.
I can't reproduce it since I don't really know what's going on, but our crash report shows a considerable amount of these issues...

FYI, we use the Sentry and NewRelic SDKs and I am not sure if they could interfere with RealHTTP somehow 🤔

OS Version: iOS 15.5 (19F77)
Report Version: 104

Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Codes: BUS_NOOP at 0x0000000000000419
Crashed Thread: 1

Application Specific Information:
addObject: > countByEnumeratingWithState:objects:count: > transactionMetrics >
Attempted to dereference garbage pointer 0x419.

Thread 1 Crashed:
0   libobjc.A.dylib                 0x33045f118         objc_retain
1   CFNetwork                       0x302374644         CFURLRequestSetShouldStartSynchronously
2   CFNetwork                       0x30237a400         CFURLCacheCurrentDiskUsage
3   CFNetwork                       0x3023783c8         CFURLRequestSetHTTPRequestBodyParts
4   ZVVOneApp                       0x203193e1c         HTTPMetrics.init (HTTPMetrics.swift:61)
5   ZVVOneApp                       0x203165858         HTTPResponse.init (HTTPResponse.swift:126)
6   ZVVOneApp                       0x203174368         [inlined] HTTPResponse.__allocating_init (HTTPResponse.swift:120)
7   ZVVOneApp                       0x203174368         HTTPDataLoader.completeTask (HTTPDataLoader.swift:434)
8   ZVVOneApp                       0x2031745cc         [inlined] HTTPDataLoader.urlSession (HTTPDataLoader.swift:268)
9   ZVVOneApp                       0x2031745cc         HTTPDataLoader.urlSession (<compiler-generated>:266)
10  NewRelic                        0x107941e28         -[NRURLSessionTaskDelegateBase URLSession:task:didCompleteWithError:] (NRMAURLSessionTaskDelegateBase.m:55)
11  CFNetwork                       0x30234fa74         CFURLRequestCopyHTTPRequestMethod
12  Foundation                      0x3041f43e8         __NSBLOCKOPERATION_IS_CALLING_OUT_TO_A_BLOCK__
13  Foundation                      0x304205a9c         -[NSBlockOperation main]
14  Foundation                      0x3041dfad8         __NSOPERATION_IS_INVOKING_MAIN__
15  Foundation                      0x3041f0018         -[NSOperation start]
16  Foundation                      0x3041f34e4         __NSOPERATIONQUEUE_IS_STARTING_AN_OPERATION__
17  Foundation                      0x304200cf8         __NSOQSchedule_f
18  libdispatch.dylib               0x300da2044         _dispatch_block_async_invoke2
19  libdispatch.dylib               0x300dc0090         _dispatch_client_callout
20  libdispatch.dylib               0x300d97ab4         _dispatch_continuation_pop$VARIANT$armv81
21  libdispatch.dylib               0x300d971f0         _dispatch_async_redirect_invoke
22  libdispatch.dylib               0x300da462c         _dispatch_root_queue_drain
23  libdispatch.dylib               0x300da4de8         _dispatch_worker_thread2
24  libsystem_pthread.dylib         0x3b7a29dd0         _pthread_wqthread

match(urlRegex:) function and the init of HTTPStubRegExMatcher() does not throws in case of wrong regular expression pattern

I use several stubs, just implemented the third one and now running into problems:

extension HTTPStubRequest {
    static var stubXY: HTTPStubRequest {
        HTTPStubRequest().match(urlRegex: "*/resources/user/*/info").stub(for: .get) { urlRequest, matchedStubRequest in

        // getting here for the url https://example.com/resources/public/countries
        ...
        }
    }
}

This should never be the case, right? I'm having another stub added before this that should handle this case.

HTTPClient and URLSessionTask are removed after the completion, also breaking the cURLDescription()

Bug Report

Once the network operation is finished both client and sessionTask of the origin request are set to nil so calling the curlDescription() function results in an error.

This happens in completeTask(_ task: URLSessionTask, error: Error?) function:

        // Reset the link to the client
        handler.request.client = nil
        handler.request.sessionTask = nil

There is no need anymore to reset these values, client is already a weak var.

HTTPAltRequestValidator does not retry the original request

Thanks for this awesome library, would love to use it in production!

The problem I'm currently facing is that when my original request returns a 401, the HTTPAltRequestValidator works as expected (i.e. it does the login call and sets it on the client), but my original request is not repeated as I see in a proxy running on my mac.

this is my request:

HTTPRequest {
                $0.url = URL(string: "https://...")
                $0.maxRetries = 3
                $0.method = .get
            }

and this is my setup:

        let client = HTTPClient.shared

        let authValidator = HTTPAltRequestValidator(statusCodes: [.unauthorized]) { request, response in
            // If triggered here you'll specify the alt call to execute in order to refresh a JWT session token
            // before any retry of the initial failed request.
            return try! Endpoint.login("...", "...").httpRequest()
        } onReceiveAltResponse: { request, response in
            // Once you have received response from your `refreshToken` call
            // you can do anything you need to use it.
            // In this example we'll set the global client's authorization header.
            if let authorizationHeader = response.headers[.authorization] {
                client.headers.set(.authorization, authorizationHeader)
            }
        }

        client.validators.insert(authValidator, at: 0)

I get to the the client.headers.set(.authorization, authorizationHeader) - but then the original request is not repeated... what am I missing here?

Thanks a lot for your help!

EXC_BAD_ACCESS with concurrent access to inner's HTTPDataLoader running tasks

Bug Report

The issue is triggered when multiple concurrent tasks try to access the dataLoaders's dictionary inside the HTTPDataLoader class. This dictionary stored in-progress tasks by using as a key the URLSessionTask instance created by the underlying URLSession and as a value the HTTPDataLoaderResponse object.

This error can trigger an EXC_BAD_ACCESS crash both on-device and on simulator.

You can reproduce this issue easily by executing a great amount of concurrent tasks:

func test_concurrentNetworkCallsCrash() async throws {
        var requests = [HTTPRequest]()
        let newClient = HTTPClient(baseURL: nil)

        for _ in 0..<100 {
            let req = try! HTTPRequest(method: .get, "https://www.apple.com")
            requests.append(req)
        }
        
        await withThrowingTaskGroup(of: HTTPResponse.self, body: { group in
            for req in requests {
                group.addTask(priority: .high) {
                    let result = try await req.fetch(newClient)
                    print(result.data?.count ?? 0)
                    return result
                }
            }
            
        })
    }

We could fix it by using a lock on write.

HTTPAltValidator not correctly triggered with multiple concurrent calls

Bug Report

This bug affect both the 0.9.x (pre-releases) and the new 1.0.0.
The bug avoid HTTPAltValidator to be triggered when multiple concurrent calls are calling the same validator. This happens because numberOfAltRequestExecuted is incremented even when no alt-request is triggered so it reach very fast the limit (maxAltRequestsToExecute).

This check should be moved right before returning the alt call.

open func validate(response: HTTPRawResponse, forRequest request: HTTPRequestProtocol) -> HTTPResponseValidatorResult {
        if let statusCode = response.error?.statusCode, triggerHTTPCodes.contains(statusCode) {
            return .passed
        }
        
        // If error is one of the errors in `triggerHTTPCodes`
        
        // If we reached the maximum number of alternate calls to execute we want to cancel any other attempt.
        if let maxAltRequestsToExecute = maxAltRequestsToExecute,
              numberOfAltRequestExecuted > maxAltRequestsToExecute {
            let error = HTTPError(.maxRetryAttemptsReached)
            return .failWithError(error)
        }
        
        guard let altOperation = requestProvider(request, response) else {
            return .passed // if no retry operation is provided we'll skip and mark the validation as passed
        }

        numberOfAltRequestExecuted += 1
        return .retryAfter(altOperation)
    }
Q A
BC Break yes
Version 0.9.x and 1.0.x

[Feature]: include underlying error for HTTP errors with retryAttemptsReached

What problem are you facing?

Hi,
I am trying to use the retry mechanism and I am wondering if it was possible to include the returned error for the last retry.

For example, I have an endpoint that I want to query until it returns a valid response. Otherwise, if it returns an error response with a specific type in it, I want to do retry. When it reaches the max retries count, I get a HTTPError with the category retryAttemptsReached, but I'd like to know if it could be possible to get the last error response that triggered the retry.

From what I see in the code, the logic is a bit weird: you send that error only at the next retry. Shouldn't it be check after the last response instead?

Other Information

No response

Serialize/deserialize in a different Task/thread

Feature Request

Watching Swift concurrency: Update a sample app (WWDC21) there is a suggestion to replace a serial DispatchQueue with an actor to perform work in background.
It’s not exactly the same thing, because actor runtime doesn’t use GCD – actors have an advantage that they use a cooperative pool of threads.
And if you want to parallelize decoding, you’ll need to look for other approaches, e.g. Task.detached.
However it's a good way to put this kind of work outside the network layer in a separate actor reducing the amount of work done on the HTTPClient class.

Q A
New Feature yes
RFC yes

Reimplement HTTPAltRequestValidator for new API set

Feature Request

This validator can be used to provide an alternate request operation to execute before retry the initial request. It may be used to provide silent login operation when you receive and authorized/forbidden errors.
It's triggered by the triggerHTTPCodes which by default is set .unathorized, .forbidden.

The old implementation is here and should be pretty straightforward to integrate into the new API design.

Q A
New Feature yes
BC Break no

Allow defining shared query params at client level

Feature Request

Sometimes you may need to append one or more query params to each call.
Actually, you need to repeat them over and over.
It could be useful to define a list of query items (URLQueryItem) at HTTPClient level you can set and it will be appended to each call executed on that client instance.

Q A
New Feature yes
RFC no
BC Break no

HTTPClient's queryParams are not added to requests that have no query parameters (aka nil)

Hi I've noticed that even if I set a bunch of query parameters on my HTTPClient instance, they are not added on the requests that don't have query parameters (i.e. request.queryItems is nil, not an empty dict) :

In HTTPRequest.swift line 536, if newComps.queryItems is nil, then the .append(contentsOf:) will not occur.

if let commonQueryParams = client?.queryParams, commonQueryParams.isEmpty == false {
    newComp.queryItems?.append(contentsOf: commonQueryParams)
 }

Not sure if it's an expected behaviour or a bug 🤔

Feel free to close this issue directly if this is intentional 😉

[Bug]: Body's specific headers overrides all the request/client set headers

Platform Version

iOS 16

SDK Version

1.7.2

Xcode Version

Xcode 14

Steps To Reproduce

I have this request

try await HTTPRequest {
            $0.path = "businesscards"
            $0.headers = HTTPHeaders([
                .authorization:"Bearer \(token)",
                .contentType:"multipart/form-data"
            ])
            
            $0.method = .post
            $0.body = try .multipart(boundary: nil, { form in
                if let name = businessCardCreation.name {
                    try form.add(string: name, name: "name")
                }
                if let surname = businessCardCreation.surname {
                    try form.add(string: surname, name: "surname")
                }
                if let email = businessCardCreation.email {
                    try form.add(string: email, name: "email")
                }
            })
        }.fetch().decode(BusinessCard.self, decoder: Decoders.decoder)

that is not sending Authorization header to the backend.

Expected Behavior

I expect to see the Authorization header in the backend.

Actual Incorrect Behavior

I can't see the authorization header in the backend.
Here is a screenshot of the headers sent in a local call, but the same happens on the cloud.

Schermata 2022-10-07 alle 07 36 56

Trying the same request using Alamofire everything works.
Schermata 2022-10-07 alle 08 02 21

How to handle errors in RealHTTP: a test case

Hello,
I did a take-home exercise for Walmart Labs and decided to use RealHTTP after doing a quick compare to other HTTP clients.
My take-home was rejected and one of the three reasons was it "doesn't handle errors in a nice way". I didn't really handle errors
as it was outside of the scope and 60 minute timeframe, but I did put in a catch:

struct APIService {
    
    struct InfoService {
        static func fetchCountries() async ->CountryInfo?  {
            do {
                guard let url = URL(string: "\(APIEnvironment.development.baseURL)/peymano-wmt/32dcb892b06648910ddd40406e37fdab/raw/db25946fd77c5873b0303b858e861ce724e0dcd0/countries.json") else { throw "Malformed URL" }
                if let countryInfo = try await HTTPRequest(url)
                    .fetch(CountryInfo.self) {
                    return countryInfo
                }
            } catch {
                //TODO: log error
            }
            return nil
        }
    }
}

class CapitalSearchViewModel {
    weak var dataSource : GenericDataSource<CountryInfoElement>?

    init(dataSource : GenericDataSource<CountryInfoElement>?) {
        self.dataSource = dataSource
    }
    
    func fetchCountries() {
        Task {
            await self.dataSource?.data.value = APIService.InfoService.fetchCountries() ?? []
        }
    }
}

Extending on what is above, what would be the preferred way to do error handling with RealHTTP? I can provide access to the project if anyone wants to take a look. I didn't really agree with the feedback and would appreciate more opinions from other iOS devs if anyone has time. My email is bhartsb at gmail.com. The other feedback was "iOS code is overly complex" and "boilerplate code". Boiler plate code was Xcode generated for the most part.

Added HTTPDynamicStubResponse to customize stub response dynamically

Feature Request

Actually, the HTTPStubRequest does not provide a way to return a dynamic HTTPStubResponse based upon the original callback received.
It could be very useful for unit testing purposes.

A possible implementation involve the creation of a generic protocol (ie. HTTPStubResponseProvider) which provide a single method with a signature like this:

func response(forURLRequest urlRequest: URLRequest, matchedStub stubRequest: HTTPStubRequest) -> HTTPStubResponse?

The following protocol allows you to define a response based upon the received URLRequest (with the additional info of the matched stub request).

In order to avoid breaking changes this protocol should replace the value on responses map of the HTTPStubRequest. From:

public var responses = [HTTPMethod: HTTPStubResponse]()

to

public var responses = [HTTPMethod: HTTPStubResponseProvider]()

The HTTPStubResponse must conform to the protocol simply return self.
At this point, a new struct called HTTPDynamicStubResponse will just implement the protocol itself with the optional init callback.

A final stub function will allow to create a dynamic behavior. For example:

let stub = HTTPStubRequest().match(urlRegex: "*").stub(for: .get, responseProvider: { request, stubRequest in
      // create and return your own dynamic response
})

Thread Safety Issue accessing to HTTPDataLoader (#2)

Bug Report

Q A
BC Break yes
Version 1.4.0

Summary

We have seen intermittent crashes in HTTPDataLoader.fetch, while writing a response to the dataLoadersMap dictionary. A cursory inspection of the code in HTTPDataLoader makes it look like this dictionary is not protected by atomic access, but it is being accessed (both read from and written to) from multiple threads. The access is inside an operation added to the session's delegate queue, but this queue is concurrent. We believe this may be why we are seeing intermittent crashes in this area.

Screen Shot 2022-05-23 at 4 33 51 PM

Custom validator example does not work because of `throw`

Hi,

I tried to implement a custom validator, based on the MyBadWSValidator example, but it doesn't compile because of the throw statements there, as the method itself does not have throws in the signature.

I don't know what's the intended solution for this; if throw is added in the signature, it affects the validate message within the HTTPClient...

Added cURLDescription also for HTTPResponse

Feature Request

cURLDescription() function should be available also in HTTPResponse instances in order to print the executed statements for the original request without passing explicitly the client.

Q A
New Feature yes
RFC no
BC Break no

Note

Due to #17 we should move the cURLDescription() function for HTTPRequest to async because the serialization of data is now made on another thread.

[Feature]: Add starting power offset for the exponential backoff

What problem are you facing?

Hi guys!
I've noticed that your implementation of the exponential backoff is returning a delay of : 0.5, 1, 2, 4s...

Would it be possible to have a way to provide a custom retry implementation along with the one that you provide? or, as a simpler change, add another parameter to let us decide the initial power parameter? For my use case, I'd need my retries to be performed after 2s, 4s and 8s.
So I was thinking about something like .exponential(base: 2, from: 1) or .exponential(base: 2, skip: 2) ..

What do you think ?

Other Information

No response

[Bug]: Query array parameters with brackets doesn't work

Platform Version

17.0

SDK Version

1.0.0

Xcode Version

15.0.1

Steps To Reproduce

var type: [Int] = [4, 2]
$0.add(parameters: ["type": type ], arrayEncoding: .noBrackets)

it doesn't work

but when I wrote like this
$0.add(queryItems: type.map({ URLQueryItem(name: "type", value: "($0)")}))
it worked

Expected Behavior

$0.add(parameters: ["type": type ], arrayEncoding: .noBrackets)
with this following parameters I expect following request
.../api/app/v1/shop/list?type=2?type=4
this code should work as you said on documentations

Actual Incorrect Behavior

$0.add(parameters: ["type": type ], arrayEncoding: .noBrackets) this doesn't work
with this following parameters I got
.../api/app/v1/shop/list?type=2

it doesn't append second type of query parameter

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.