-
Notifications
You must be signed in to change notification settings - Fork 98
Implementing cross-platform support by supporting Linux-friendly HTTP clients #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a few comments, mostly with the goal to not change anything on non Linux platforms other than adding a layer of indirection that is fine.
If this merges, you might want to follow up with adding some CI on Linux (maybe just build and run tests) to prevent future breakages as I don't think a lot of people would think about testing on this platform.
dependencies: [ | ||
.package(url: "https://github.com/swift-server/async-http-client.git", from: "1.25.2"), | ||
], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is possible, but can we scope this to only linux for now? ie wrap in #if os(Linux)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made this change by using condition: .when(platforms: [.linux])
, since #if os(Linux)
doesn't work in Package.swift.
let httpClient: HTTPClient | ||
let session: URLSession |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not super familiar with the code, but it seems weird to see both HTTPClient and URLSession properties here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not remove the session
property, the NoOpClient
type, and do
self.httpClient = URLSessionHTTPClientAdapter(urlSession: URLSession(
configuration: .default,
delegate: aiproxySecureDelegate,
delegateQueue: nil))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a good idea, removed the NoOpClient
in favor of your solution!
import NIOHTTP1 | ||
|
||
/// Adapter that implements HTTPClient protocol using AsyncHTTPClient | ||
public class AsyncHTTPClientAdapter: HTTPClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe to reduce the scope of the change in this PR, what do you think of:
- scope this code to only Linux.
- replaces usages of
AsyncHTTPClientAdapter.createDefault()
byHTTPClient.createDefault()
- add
extension HTTPClient {
static func createDefault() -> HttpClient {
#if os(Linux)
AsyncHTTPClientAdapter.createDefault()
#else
URLSessionHTTPClientAdapter()
#endif
}
/// - decoder: The JSON decoder to be used for parsing API responses (default is `JSONDecoder.init()`). | ||
/// - httpClient: The HTTPClient to be used for network calls. Defaults to `AsyncHTTPClientAdapter.createDefault()` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jamesrochabrun how do you feel about changing existing APIs here?
Would you prefer to keep the existing ones available outside of Linux, and have them map to the new ones, at the cost of having two functions that mostly do the same thing, or are you fine with the breaking change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its better to have it as another initializer, I would like to avoid breaking changes if possible.
|
||
// Convert URLRequest to HTTPRequest | ||
let httpRequest = HTTPRequest( | ||
url: request.url!, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the !
always looke scary
guard let httpResponse = response as? HTTPURLResponse else { | ||
throw APIError.requestFailed(description: "invalid response unable to get a valid HTTPURLResponse") | ||
} | ||
printHTTPURLResponse(httpResponse) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: this is removed
let httpRequest = HTTPRequest( | ||
url: request.url!, | ||
method: HTTPMethod(rawValue: request.httpMethod ?? "GET") ?? .get, | ||
headers: request.allHTTPHeaderFields ?? [:], | ||
body: request.httpBody | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this is repeated several times. Maybe add an initializer in HTTPRequest
from URLRequest
?
These are all great suggestions, I'll address them in the next couple of days. I should have noted this but I had left in the AsyncHTTPClient code on iOS/macOS so changes could be tested by people on Apple platforms, but happy to contain everything AsyncHTTPClient-related to Linux only. |
Thanks @mergesort for your willingness to address @gsabran feedback! |
Sorry for the time it took, but I believe I've addressed all of the concerns. Please let me know if I've missed anything — excited to have this functionality on the server! |
@mergesort this is awesome, thanks for addressing the feedback, I will defer to @gsabran to do the final review, but can I please ask you to add a section in the Read me in how to use this new functionality? it will be nice to show it for others. Thanks! |
Thank you for your work! I have tried to build docker-image (vapor+swiftopenai) and faced with a lot of errors. I found a couple of places in code where use UIKit/AppKit to work with images and build fails here:
Missing arguments error for HTTPRequest:
I just wanted to highlight these places because they might be important. |
@AlekseyPleshkov I'm so sorry about that, I had been testing against my app's server, which I plan to deploy this on. I thought everything had worked on Linux, but only now did I realize that in my haste to push a new feature last week I had commented out an internal import of the package containing SwiftOpenAI, so I was seeing successful builds when they would have failed. I've created a docker image locally for testing, and am once again trying to resolve the issues you're seeing. You may see a few pushes from me, especially as I work through adding Linux testing on CI (CI is not a strength of mine), so please excuse me if that doesn't work yet and you get a few notifications about me pushing changes. @jamesrochabrun Yes, will absolutely add some notes to the readme before considering this done. 🙂 |
I've fixed all the errors (tested both in Docker and on my server), updated the readme, and the code should be working as expected now. Please let me know if this all works for you — I think I've addressed all of the concerns but I'm happy to make any other changes as needed. As I mentioned, I'm not great at CI, so I used Claude Code to update the
Note There was one more correction I needed to make after that, running the linter to address all of the code style requirements. I noticed that this is a recent addition to the repo, and I thought Claude made a good suggestion, to automatically run linting as part of the PR process. While it's not something to do for this PR, I figured I'd attach the suggested auto-formatting version of the |
This PR adds support for Linux, as requested in #89. It does so by:
Note
These changes are not applied to any of the code related to AIProxy. I've left everything alone there as best as I can, and simply do not compile any AIProxy code on Linux, since AIProxy does not make much sense to use in a server environment where API requests are already protected.
Please let me know what you think, happy to make any changes you request. Would love to get this merged in so I can start using SwiftOpenAI in my server-side app!