how to set a default http header with alamofire - alamofire

I want to set a header so that all requests include an authorized token header once the user logs in. I thought I could set the header in the shared alamofire manager by calling my setToken function but I don't think I'm doing it correctly. What's my mistake? Is there a better way of setting a default header for all alamofire requests?
class UserService : NSObject {
static let manager: Alamofire.Manager = Alamofire.Manager(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
class func setToken(token:String){
manager.session.configuration.HTTPAdditionalHeaders = [
"x-token": token
]
}
class func addFriend(user:LoggedUser, uname:String, callback:((success: Bool, errorMsg: String?)->Void)) {
let params:[String : AnyObject] = ["uname": uname]
let url = AppConfig.sharedInstance().baseURL() + "/user/\(user.userId!)/friends"
Alamofire.request(.POST, url , parameters:params, encoding:.JSON)
.responseJSON(options: .MutableContainers, completionHandler:{ (request, response, JSON, error) -> Void in
//token is not set in the request
}
}

Your code never make use of your custom manager, use following code:
class CustomManager: Manager {
static public let manager = CustomManager.generateManager()
class func generateManager()-> CustomManager {
var defaultHeaders = Alamofire.Manager.defaultHTTPHeaders ?? [:]
defaultHeaders["x-token""] = "token"
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders
let manager = CustomManager(configuration: configuration)
return manager
}
}
class UserService : NSObject {
class func addFriend(user:LoggedUser, uname:String, callback:((success: Bool, errorMsg: String?)->Void)) {
let params:[String : AnyObject] = ["uname": uname]
let url = AppConfig.sharedInstance().baseURL() + "/user/\(user.userId!)/friends"
CustomManager.manager(.POST, url , parameters:params, encoding:.JSON)
.responseJSON(options: .MutableContainers, completionHandler:{ (request, response, JSON, error) -> Void in
// ...
}
}

If you were using a custom SessionManager in Alamofire 4, now in Alamofire 5 it's called Session and you can create it this way:
let configuration = URLSessionConfiguration.default
var defaultHeaders = HTTPHeaders.default
defaultHeaders["X-API-Version"] = "1.0"
configuration.headers = defaultHeaders
let yourManager = Alamofire.Session(configuration: configuration)

So my mistake was using the class function rather than the shared instance when I made the request.
So it should be
Alamofire.Manager.sharedInstance.request(.POST, url , parameters:params, encoding:.JSON)
rather than
Alamofire.request(.POST, url , parameters:params, encoding:.JSON)

Create a class that conforms to protocol RequestAdapter. You will have to implement this function func adapt(_ urlRequest: URLRequest) throws -> URLRequest {. Inside it do:
func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
var urlRequest = urlRequest
urlRequest.setValue("YourUserAgent", forHTTPHeaderField: "User-Agent")
return urlRequest
}
In this function you can add whatever headers you want.
And then do:
sessionManager.adapter = "Class that conforms to RequestAdapter", where sessionManager is your Alamofire SessionManager.

Related

How to update nested structure of a published variable using another api call

I have a published var called spotWallet in my BinanceStore class which conforms to observaleObject. I can create a publisher and subscribe to it. but there is property in my top level struct called balances which is an array of Balance struct. What I want to achieve is would like to update a property of Balance called price which comes from another network call. I cannot get that to work, in clean combine swiftui way.
class BinanceStore: ObservableObject, Identifiable {
#Published var spotWallet: SpotWallet?
#Published var loadingError: String = ""
#Published var showAlert: Bool = false
private var cancellableSet: Set<AnyCancellable> = []
private let binanceFetcher: BinanceFetchable
init(binanceFetcher: BinanceFetchable) {
self.binanceFetcher = binanceFetcher
}
func AccountDetailsOnSpot() async {
binanceFetcher.getAccountDetailsOnSpot()
.sink { (dataResponse) in
if dataResponse.error != nil {
self.createAlert(with: dataResponse.error!)
} else {
self.spotWallet = dataResponse.value!
}
}.store(in: &cancellableSet)
}
func createAlert( with error: NetworkError ) {
loadingError = error.localizedDescription
self.showAlert = true
}
}
struct SpotWallet: Codable, Hashable {
let makerCommission, takerCommission, buyerCommission, sellerCommission: Int
let canTrade, canWithdraw, canDeposit: Bool
let updateTime: Int
let accountType: String
let balances: [Balance]
let permissions: [String]
}
protocol BinanceFetchable {
func getAccountDetailsOnSpot() -> AnyPublisher<DataResponse<SpotWallet, NetworkError>, Never>
}
class BinanceFetcher {
var coinPublisher = PassthroughSubject<CoinBalance, Never>()
}
extension BinanceFetcher: BinanceFetchable {
func getAccountDetailsOnSpot() -> AnyPublisher<DataResponse<SpotWallet, NetworkError>, Never> {
let endpoint = "/api/v3/account"
let params = BinanceWrapper.shared.makeRequestReady(queries: nil)
let url = URL(string: "\(BinanceWrapper.BINANCE_BASE_URL)\(endpoint)")!
return AF.request(url, parameters: params, encoding: URLEncoding.default, headers: BinanceWrapper.BINANCE_HTTP_HEADERS)
.validate()
.publishDecodable(type: SpotWallet.self)
.map { response in
response.mapError { error in
return NetworkError.parsing(description: error.localizedDescription)
}
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

How does a view obtain data using a view model and Network API

I'm trying to fetch some data with this helper file:
https://gist.github.com/jbfbell/e011c5e4c3869584723d79927b7c4b68
Here's a snippet of the important code:
Class
/// Base class for requests to the Alpha Vantage Stock Data API. Intended to be subclasssed, but can
/// be used directly if library does not support a new api.
class AlphaVantageRequest : ApiRequest {
private static let alphaApi = AlphaVantageRestApi()
let method = "GET"
let path = ""
let queryStringParameters : Array<URLQueryItem>
let api : RestApi = AlphaVantageRequest.alphaApi
var responseJSON : [String : Any]? {
didSet {
if let results = responseJSON {
print(results)
}
}
}
}
Extension ApiRequest
/// Makes asynchronous call to fetch response from server, stores response on self
///
/// - Returns: self to allow for chained method calls
public func callApi() -> ApiRequest {
guard let apiRequest = createRequest() else {
print("No Request to make")
return self
}
let session = URLSession(configuration: URLSessionConfiguration.ephemeral)
let dataTask = session.dataTask(with: apiRequest) {(data, response, error) in
guard error == nil else {
print("Error Reaching API, \(String(describing: apiRequest.url))")
return
}
self.receiveResponse(data)
}
dataTask.resume()
return self
}
My goal is to fetch the data from responseJSON after the data of the url request is loaded.
My ViewModel currently looks like this:
class CompanyViewModel: ObservableObject {
var companyOverviewRequest: ApiRequest? {
didSet {
if let response = companyOverviewRequest?.responseJSON {
print(response)
}
}
}
private var searchEndpoint: SearchEndpoint
init(companyOverviewRequest: AlphaVantageRequest? = nil,
searchEndpoint: SearchEndpoint) {
self.companyOverviewRequest = CompanyOverviewRequest(symbol: searchEndpoint.symbol)
}
func fetchCompanyOverview() {
guard let request = self.companyOverviewRequest?.callApi() else { return }
self.companyOverviewRequest = request
}
}
So in my ViewModel the didSet gets called once but not when it should store the data. The results of AlphaVantageRequest always prints out properly, but not in my ViewModel. How can I achieve to have the loaded data also in my ViewModel?
When you use a view model which is an ObservableObject, your view wants to observe published properties, usually a viewState (MVVM terminology):
class CompanyViewModel: ObservableObject {
enum ViewState {
case undefined
case value(Company)
}
#Published var viewState: ViewState = .undefined
viewState completely describes how your view will be rendered. Note, that it can be undefined - which your view should be able to handle.
Adding a loading(Company?) case would also be a good idea. Your view can then render a loading indicator. Note that loading also provides an optional company value. You can then render a "refresh", in which case you already have a company value while also drawing a loading indicator.
In order to fetch some data from an endpoint, you may use the following abstraction:
public protocol HTTPClient: class {
func publisher(for request: URLRequest) -> AnyPublisher<HTTPResponse, Swift.Error>
}
This can be implemented by a simple wrapper around URLSession with 5 lines of code. A conforming type may however do much more: it may handle authentication, authorization, it may retry requests, refresh access tokens, or present user interfaces where the user needs to authenticate, etc. This simple protocol is sufficient for all this.
So, how does your ViewModel get the data?
It makes sense to introduce another abstraction: "UseCase" which performs this task, and not let the view model directly use the HTTP client.
A "use case" is simply an object that performs a task, taking an input and producing an output or error. You can name it how you want, "DataProvider", "ContentProvider" or something like this. "Use Case" is a well known term, though.
Conceptually, it has a similar API as an HTTP client, but semantically it sits on a higher level:
public protocol UseCase {
associatedtype Input: Encodable
associatedtype Output: Decodable
associatedtype Error
func callAsFunction(with input: Input) -> AnyPublisher<Output, Error>
}
Lets create us a "GetCompany" use case:
struct Company: Codable {
var name: String
var id: Int
}
struct GetCompanyUseCase: UseCase {
typealias Input = Int
typealias Output = Company
typealias Error = Swift.Error
private let httpClient: HTTPClient
init(httpClient: HTTPClient) {
self.httpClient = httpClient
}
func callAsFunction(with id: Int) -> AnyPublisher<Company, Swift.Error> {
let request = composeURLRequest(input: id)
return httpClient.publisher(for: request)
.tryMap { httpResponse in
switch httpResponse {
case .success(_, let data):
return data
default:
throw "invalid status code"
}
}
.decode(type: Company.self, decoder: JSONDecoder())
.map { $0 } // no-op, usually you receive a "DTO.Company" value and transform it into your Company type.
.eraseToAnyPublisher()
}
private func composeURLRequest(input: Int) -> URLRequest {
let url = URL(string: "https://api.my.com/companies?id=\(input)")!
return URLRequest(url: url)
}
}
So, this Use Case clearly accesses our HTTP client. We can implement this accessing CoreData, or read from file, or using a mock, etc. The API is always the same, and the view model does not care. The beauty here is, you can switch it out and swap in another one, the view model still works and also your view. (In order to make this really cool, you would create a AnyUseCase generic type, which is very easy, and here you have your dependency injection).
Now lets see how the view model may look like and how it uses the Use Case:
class CompanyViewModel: ObservableObject {
enum ViewState {
case undefined
case value(Company)
}
#Published var viewState: ViewState = .undefined
let getCompany: GetCompanyUseCase
var getCompanyCancellable: AnyCancellable?
init(getCompany: GetCompanyUseCase) {
self.getCompany = getCompany
}
func load() {
self.getCompanyCancellable =
self.getCompany(with: 1)
.sink { (completion) in
print(completion)
} receiveValue: { (company) in
self.viewState = .value(company)
print("company set to: \(company)")
}
}
}
The load function triggers the use case, which calls the underlying http client to load the company data.
When the UseCase returns a company, it will be assigned the view state. Observers (the view, or ViewController) will get notified about the change and can preform an update.
You can experiment with code in playground. Here are the missing peaces:
import Foundation
import Combine
extension String: Swift.Error {}
public enum HTTPResponse {
case information(response: HTTPURLResponse, data: Data)
case success(response: HTTPURLResponse, data: Data)
case redirect(response: HTTPURLResponse, data: Data)
case clientError(response: HTTPURLResponse, data: Data)
case serverError(response: HTTPURLResponse, data: Data)
case custom(response: HTTPURLResponse, data: Data)
}
class MockHTTPClient: HTTPClient {
func publisher(for request: URLRequest) -> AnyPublisher<HTTPResponse, Swift.Error> {
let json = #"{"id": 1, "name": "Some Corporation"}"#.data(using: .utf8)!
let url = URL(string: "https://api.my.com/companies")!
let httpUrlResponse = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil)!
let response: HTTPResponse = .success(response: httpUrlResponse, data: json)
return Just(response)
.mapError { _ in "no error" }
.eraseToAnyPublisher()
}
}
Assemble:
let httpClient = MockHTTPClient()
let getCompany = GetCompany(httpClient: httpClient)
let viewModel = CompanyViewModel(getCompany: getCompany)
viewModel.load()

RxAlamofire - how to get the response on error?

I need the response body when an error occurs in an RxAlamofire call. I've seen this hack but I wonder if there's a cleaner way.
Inspired by it, I created this RxAlamofire fork with a similar hack. With it, errors will usually be an instance of DataResponseError so you can do this:
RxAlamofire.data(method, url).subscribe(
onError: { error in
if let error = error as? DataResponseError<Data> {
// Get response body (in this case, convert it to a String)
if let data = error.response.data {
let message = String(data: data, encoding: String.Encoding.utf8)
print("Message: \(message)")
}
// Get status code
if let statusCode = error.response.response?.statusCode {
print("Status code: \(statusCode)")
}
}
}
)
Issue description. I'm using RxAlamofire to make network requests, and I needed to get information from the body of the error response.
I've made a hack in a folloing way:
Added a PError:
import UIKit
import Alamofire
import ObjectMapper
class PError: Error, Mappable {
var message: String?
var statusCode: Int?
required init?(map: Map) {
}
func mapping( map: Map) {
message <- map["error.message"]
statusCode <- map["error.statusCode"]
}
}
And now added such extensions to DataRequest:
import Alamofire
import ObjectMapper
extension DataRequest {
//Overriding several methods from Alamofire Validation
#discardableResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { [unowned self] _, response, bodyData in
return self.validate(statusCode: acceptableStatusCodes, response: response, bodyData: bodyData)
}
}
//Overriding several methods from Alamofire Validataion
fileprivate func validate<S: Sequence>(
statusCode acceptableStatusCodes: S,
response: HTTPURLResponse, bodyData: Data?)
-> ValidationResult
where S.Iterator.Element == Int
{
if acceptableStatusCodes.contains(response.statusCode) {
return .success
} else {
var error: Error = AFError.responseValidationFailed(reason: AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: response.statusCode))
if let bodyData = bodyData {
if let jsonString = String(data: bodyData, encoding: .utf8) {
if let errorNew = Mapper<PError>().map(JSONString: jsonString)
{
error = errorNew
}
}
}
return .failure(error)
}
}
}
Next, somewhere in the code you'll be able to work with this custom error object:
if let err = error as? PError {
status = err.message ?? "No message in the error description"
}
else

JAX-RS : HTTP Status 415 - Unsupported Media Type

I got this error when making an ajax request to my WebService :
HTTP Status 415 - Unsupported Media Type
I tried to add the good MediaType (Text/Html, i think), but it doesn't work. I have still this error. What could this be, do you think ?
Thank you !
My request :
$(document).on('submit','.form-add-edit',function(e){
e.preventDefault();
var idDisruptive = $(e.target).find('input[name=idDisruptive]').val();
var url = "api/disruptive";
var method = "POST";
if (idDisruptive){
url = url + '/' + idDisruptive;
method = "PUT";
}
$.ajax({
url: url,
method : method,
data : getDisruptiveParams(),
success : function (response){
console.log('EDIT')
console.log(response);
//editDisruptive(response);
},
error : function(response){
console.log('EDIT ERROR')
console.log(response);
}
});
});
The Web Service :
#Stateless
#Path("disruptive")
#Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_HTML, MediaType.APPLICATION_XML, MediaType.APPLICATION_FORM_URLENCODED})
#Produces({MediaType.APPLICATION_JSON})
public class DisruptiveFacadeREST extends AbstractFacade<Disruptive> {
#PersistenceContext(unitName = "StoryGeneratorPU")
private EntityManager em;
public DisruptiveFacadeREST() {
super(Disruptive.class);
}
#POST
#Override
public void create(Disruptive entity) {
super.create(entity);
}
#PUT
#Path("{id}")
public void edit(#PathParam("id") Integer id, Disruptive entity) {
super.edit(entity);
}
#Override
protected EntityManager getEntityManager() {
return em;
}
}
You need to set the content-type on the jQuery request. If you don't, it will default to application/x-www-form-urlencoded. And just because you add #Consumes(MediaType.APPLICATION_FORM_URLENCODED) doesn't mean that JAX-RS will not how to convert the form data to Disruptive. There need to be a MessageBodyReader to handle that conversion, which there isn't. Same goes for MediaType.TEXT_HTML. Just adding that means nothing if there is no converter to handle the conversion. Remove those two. What you want is to handle JSON conversion, and there should already be a MessageBodyReader included in the EE server that will convert JSON data to arbitrary POJOs.
So for the jQuery, just add
$.ajax({
contentType: 'application/json'
})
That should solve the problem.

Alamofire 3.0.0-beta.3 Image Response Serialization

Can some one help me please, I'm trying to implement Alamofire image response serialization method : imageResponseSerializer,
Here is my code :
extension Alamofire.Request {
class func imageResponseSerializer() -> ResponseSerializer<UIImage, NSError> {
return ResponseSerializer<UIImage, NSError> { request, response, data, error in
guard let validData = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
if let image = UIImage(data: validData, scale: UIScreen.mainScreen().scale) {
return Result<UIImage, NSError>.Success(image)
}
else {
return .Failure(Error.errorWithCode(.JSONSerializationFailed, failureReason: "Unable to create image."))
}
}
}
func responseImage(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<UIImage, NSError>) -> Void) -> Self {
return response(responseSerializer: Request.imageResponseSerializer(), completionHandler: { request, response, result in
completionHandler(request, response, result)
})
}
}
Error : cannot call value of non-function type 'NSHTTPURLResponse?'
I'm using : Xcode 7.0.1, Swift 2 and Alamofire 3.0.0-beta.3
Thank you,
You should really check out AlamofireImage. 1) It has all this implemented already. 2) It has many other awesome features that you will most likely find handy.