Giter Site home page Giter Site logo

networking's Introduction

Networking

Networking was born out of the necessity of having a networking library that has a straightforward API that supports faking requests and caching images out of the box.

  • Friendly API
  • Singleton free
  • No external dependencies
  • Minimal implementation
  • Fully unit tested
  • Simple request cancellation
  • Fake requests easily (mocking/stubbing)
  • Flexible caching
  • Image downloading

Table of Contents

Choosing a configuration

Initializing an instance of Networking means you have to select a URLSessionConfiguration. The available types are .default, .ephemeral and .background, if you don't provide any or don't have special needs then default will be used.

  • .default: The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain.

  • .ephemeral: An ephemeral session configuration object is similar to a default session configuration object except that the corresponding session object does not store caches, credential stores, or any session-related data to disk. Instead, session-related data is stored in RAM. The only time an ephemeral session writes data to disk is when you tell it to write the contents of a URL to a file. The main advantage to using ephemeral sessions is privacy. By not writing potentially sensitive data to disk, you make it less likely that the data will be intercepted and used later. For this reason, ephemeral sessions are ideal for private browsing modes in web browsers and other similar situations.

  • .background: This configuration type is suitable for transferring data files while the app runs in the background. A session configured with this object hands control of the transfers over to the system, which handles the transfers in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app itself is suspended or terminated.

// Default
let networking = Networking(baseURL: "http://httpbin.org")

// Ephemeral
let networking = Networking(baseURL: "http://httpbin.org", configuration: .ephemeral)

Changing request headers

You can set the headerFields in any networking object.

This will append (if not found) or overwrite (if found) what NSURLSession sends on each request.

networking.headerFields = ["User-Agent": "your new user agent"]

Authenticating

HTTP basic

To authenticate using basic authentication with a username "aladdin" and password "opensesame" you only need to do this:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(username: "aladdin", password: "opensesame")
let result = try await networking.get("/basic-auth/aladdin/opensesame")
// Successfully authenticated!

Bearer token

To authenticate using a bearer token "AAAFFAAAA3DAAAAAA" you only need to do this:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(token: "AAAFFAAAA3DAAAAAA")
let result = try await networking.get("/get")
// Successfully authenticated!

Custom authentication header

To authenticate using a custom authentication header, for example "Token token=AAAFFAAAA3DAAAAAA" you would need to set the following header field: Authorization: Token token=AAAFFAAAA3DAAAAAA. Luckily, Networking provides a simple way to do this:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(headerValue: "Token token=AAAFFAAAA3DAAAAAA")
let result = try await networking.get("/get")
// Successfully authenticated!

Providing the following authentication header Anonymous-Token: AAAFFAAAA3DAAAAAA is also possible:

let networking = Networking(baseURL: "http://httpbin.org")
networking.setAuthorizationHeader(headerKey: "Anonymous-Token", headerValue: "AAAFFAAAA3DAAAAAA")
let result = try await networking.get("/get")
// Successfully authenticated!

Making a request

The basics

Making a request is as simple as just calling get, post, put, or delete.

GET example:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.get("/get")
switch result {
case .success(let response):
    let json = response.dictionaryBody
    // Do something with JSON, you can also get arrayBody
case .failure(let response):
    // Handle error
}

POST example:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/post", parameters: ["username" : "jameson", "password" : "secret"])
 /*
 {
     "json" : {
         "username" : "jameson",
         "password" : "secret"
     },
     "url" : "http://httpbin.org/post",
     "data" : "{"password" : "secret","username" : "jameson"}",
     "headers" : {
         "Accept" : "application/json",
         "Content-Type" : "application/json",
         "Host" : "httpbin.org",
         "Content-Length" : "44",
         "Accept-Language" : "en-us"
     }
 }
 */

You can get the response headers inside the success.

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.get("/get")
switch result {
case .success(let response):
    let headers = response.allHeaderFields
    // Do something with headers
case .failure(let response):
    // Handle error
}

The NetworkingResult type

The NetworkingResult type is an enum that has two cases: success and failure. The success case has a response, the failure case has an error and a response, none of these ones are optionals.

Here's how to use it:

// The best way
let networking = Networking(baseURL: "http://fakerecipes.com")
let result = try await networking.get("/recipes")
switch result {
case .success(let response):
    // We know we'll be receiving an array with the best recipes, so we can just do:
    let recipes = response.arrayBody // BOOM, no optionals. [[String: Any]]

    // If we need headers or response status code we can use the HTTPURLResponse for this.
    let headers = response.headers // [String: Any]
case .failure(let response):
    // Non-optional error ✨
    let errorCode = response.error.code

    // Our backend developer told us that they will send a json with some
    // additional information on why the request failed, this will be a dictionary.
    let json = response.dictionaryBody // BOOM, no optionals here [String: Any]

    // We want to know the headers of the failed response.
    let headers = response.headers // [String: Any]
}

And that's how we do things in Networking without optionals.

Choosing a Content or Parameter Type

The Content-Type HTTP specification is so unfriendly, you have to know the specifics of it before understanding that content type is really just the parameter type. Because of this Networking uses a ParameterType instead of a ContentType. Anyway, here's hoping this makes it more human friendly.

JSON

Networking by default uses application/json as the Content-Type, if you're sending JSON you don't have to do anything. But if you want to send other types of parameters you can do it by providing the ParameterType attribute.

When sending JSON your parameters will be serialized to data using NSJSONSerialization.

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/post", parameters: ["name" : "jameson"])
// Successfull post using `application/json` as `Content-Type`

URL-encoding

If you want to use application/x-www-form-urlencoded just use the .formURLEncoded parameter type, internally Networking will format your parameters so they use Percent-encoding or URL-enconding.

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/post", parameterType: .formURLEncoded, parameters: ["name" : "jameson"])
// Successfull post using `application/x-www-form-urlencoded` as `Content-Type`

Multipart

Networking provides a simple model to use multipart/form-data. A multipart request consists in appending one or several FormDataPart items to a request. The simplest multipart request would look like this.

let networking = Networking(baseURL: "https://example.com")
let imageData = UIImagePNGRepresentation(imageToUpload)!
let part = FormDataPart(data: imageData, parameterName: "file", filename: "selfie.png")
let result = try await networking.post("/image/upload", part: part)
// Successfull upload using `multipart/form-data` as `Content-Type`

If you need to use several parts or append other parameters than aren't files, you can do it like this:

let networking = Networking(baseURL: "https://example.com")
let part1 = FormDataPart(data: imageData1, parameterName: "file1", filename: "selfie1.png")
let part2 = FormDataPart(data: imageData2, parameterName: "file2", filename: "selfie2.png")
let parameters = ["username" : "3lvis"]
let result = try await networking.post("/image/upload", parts: [part1, part2], parameters: parameters)
// Do something

FormDataPart Content-Type:

FormDataPart uses FormDataPartType to generate the Content-Type for each part. The default FormDataPartType is .Data which adds the application/octet-stream to your part. If you want to use a Content-Type that is not available between the existing FormDataPartTypes, you can use .Custom("your-content-type).

Others

At the moment Networking supports four types of ParameterTypes out of the box: JSON, FormURLEncoded, MultipartFormData and Custom. Meanwhile JSON and FormURLEncoded serialize your parameters in some way, Custom(String) sends your parameters as plain NSData and sets the value inside Custom as the Content-Type.

For example:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.post("/upload", parameterType: .Custom("application/octet-stream"), parameters: imageData)
// Successfull upload using `application/octet-stream` as `Content-Type`

Cancelling a request

Using path

Cancelling any request for a specific path is really simple. Beware that cancelling a request will cause the request to return with an error with status code URLError.cancelled.

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.get("/get")
// Cancelling a GET request returns an error with code URLError.cancelled which means cancelled request

// In another place
networking.cancelGET("/get")

Faking a request

Faking a request means that after calling this method on a specific path, any call to this resource, will return what you registered as a response. This technique is also known as mocking or stubbing.

Faking with successfull response:

let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
networking.fakeGET("/stories", response: [["id" : 47333, "title" : "Site Design: Aquest"]])
let result = try await networking.get("/stories")
// JSON containing stories

Faking with contents of a file:

If your file is not located in the main bundle you have to specify using the bundle parameters, otherwise NSBundle.mainBundle() will be used.

let networking = Networking(baseURL: baseURL)
networking.fakeGET("/entries", fileName: "entries.json")
let result = try await networking.get("/entries")
// JSON with the contents of entries.json

Faking with status code:

If you do not provide a status code for this fake request, the default returned one will be 200 (SUCCESS), but if you do provide a status code that is not 2XX, then Networking will return an NSError containing the status code and a proper error description.

let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
networking.fakeGET("/stories", response: nil, statusCode: 500)
let result = try await networking.get("/stories")
// error with status code 500

Downloading and caching an image

Downloading:

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.downloadImage("/image/png")
// Do something with the downloaded image

Cancelling:

let networking = Networking(baseURL: baseURL)
let result = try await networking.downloadImage("/image/png")
// Cancelling an image download returns an error with code URLError.cancelled which means cancelled request

networking.cancelImageDownload("/image/png")

Caching:

Networking uses a multi-cache architecture when downloading images, the first time the downloadImage method is called for a specific path, it will store the results in disk (Documents folder) and in memory (NSCache), so in the next call it will return the cached results without hitting the network.

let networking = Networking(baseURL: "http://httpbin.org")
let result = try await networking.downloadImage("/image/png")
// Image from network
   
let result = try await networking.downloadImage("/image/png")
// Image from cache

If you want to remove the downloaded image you can do it like this:

let networking = Networking(baseURL: "http://httpbin.org")
let destinationURL = try networking.destinationURL(for: "/image/png")
if let path = destinationURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) {
   try NSFileManager.defaultManager().removeItemAtPath(path)
}

Faking:

let networking = Networking(baseURL: baseURL)
let pigImage = UIImage(named: "pig.png")!
networking.fakeImageDownload("/image/png", image: pigImage)
let result = try await networking.downloadImage("/image/png")
// Here you'll get the provided pig.png image

Logging errors

Any error catched by Networking will be printed in your console. This is really convenient since you want to know why your networking call failed anyway.

For example a cancelled request will print this:

========== Networking Error ==========

Cancelled request: https://api.mmm.com/38bea9c8b75bfed1326f90c48675fce87dd04ae6/thumb/small

================= ~ ==================

A 404 request will print something like this:

========== Networking Error ==========

*** Request ***

Error 404: Error Domain=NetworkingErrorDomain Code=404 "not found" UserInfo={NSLocalizedDescription=not found}

URL: http://httpbin.org/posdddddt

Headers: ["Accept": "application/json", "Content-Type": "application/json"]

Parameters: {
  "password" : "secret",
  "username" : "jameson"
}

Data: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server.  If you entered the URL manually please check your spelling and try again.</p>


*** Response ***

Headers: ["Content-Length": 233, "Server": nginx, "Access-Control-Allow-Origin": *, "Content-Type": text/html, "Date": Sun, 29 May 2016 07:19:13 GMT, "Access-Control-Allow-Credentials": true, "Connection": keep-alive]

Status code: 404 — not found

================= ~ ==================

To disable error logging use the flag disableErrorLogging.

let networking = Networking(baseURL: "http://httpbin.org")
networking.disableErrorLogging = true

Installing

Networking is available through Swift Package Manager.

Author

This library was made with love by @3lvis.

License

Networking is available under the MIT license. See the LICENSE file for more info.

Attribution

The logo typeface comes thanks to Sanid Jusić.

networking's People

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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

networking's Issues

Better response faking when using parameters

For example when talking to a bot I would expect certain reply, the reply depends on the parameters of my request, not on only on the path, so it would be great if Networking had support for this.

Example:

networking.fakePOST(path: "/message", matchedParameters: ["message": "hello"], parameters: ["reply": "what's up"])

Support for url parameters and custom request headers

Having support for url parameters and custom request headers would be great!

Should be pretty straightforward. Something along the lines of:

public func GET(path: String, params: [String: AnyObject], headers: [String: String], completion: (JSON: AnyObject?, error: NSError?) -> ())

What do you think?

Ephemeral configuration

Provide support for ephemeral configuration. Useful for private browsing modes and silly backends that don't provide proper cache validation.

Consider handling image cache retrieval under the hood

The README states:

Retrieving cached image:

First download an image like you normally do:

let networking = Networking(baseURL: baseURL)
networking.downloadImage("/image/png") { image, error in
    // Downloaded image
}

Then you would be able to retrieve the image from the cache at any time!

networking.imageFromCache("/image/png") { image in
    // Image from cache, you will get `nil` if no image is found
}

I would argue that as a user of the library I'd rather always call networking.downloadImage() and be completely agnostic to whether it came from network or cache. Otherwise I litter if !cached checks before all my download calls.

api改了?

networking.POST("/post", parameters: ["MobilePhone" : name.text]) { JSON, error in
// Successfull post using application/json as Content-Type
}

这个提示不行啊

Handle completion blocks with status codes for better error handling

For example if I get an error with status code 401 I might want to show the user a better error saying "You're Unauthorized" and then logging him out if he was doing any weird task, because this means that he probably changed his password and doesn't matter what he does he's not going to be able to use the app anyway, because his credentials are invalid.

It would be even better if Networking provided friendly error messages in every existing language.

Automatic validation

Automatically validates status code within 200...299 range, and that the Content-Type header of the response matches the Accept header of the request, if one is provided.

Faking requests with pattern matching

Right now faking a request requires you to send the explicit path to be faked, but it would be great if instead you could provide a pattern.

For example, faking getting the info of a user:

networking.fakeGET("/users/315", completion: { JSON, error in
    // Do something with result
})

But what if I want to fake not only the info of user 315 but all the users?

That's why it would be great to also been able to do this:

networking.fakeGET("/users/{userID}", completion: { JSON, error in
    // Do something with result
})

Improve error logging

Remove "Optional(" from URL:

========== Networking Error ==========

*** Request ***

Error 500: Error Domain=NetworkingErrorDomain Code=500 "internal server error" UserInfo={NSLocalizedDescription=internal server error}

URL: Optional("https://xx.xx.xxx/xxx/v1/device/45d38e46f8ba4bdeb90c395b9cb53252/getOrCreate")

Headers: ["Accept": "application/json", "Authorization": "Bearer xxx", "Content-Type": "application/x-www-form-urlencoded"]

Parameters: type=IPHONE&name=Elvis%E2%80%99s%20iMac

Data: {}

*** Response ***

Headers: ["Strict-Transport-Security": max-age= 31536000, "Date": Sun, 18 Sep 2016 16:11:21 GMT, "Content-Type": application/json, "X-FRAME-OPTIONS": SAMEORIGIN, "Content-Length": 2, "Cache-Control": must-revalidate,no-cache,no-store]

Status code: 500 — internal server error

================= ~ ==================

Return always JSON when its processable

I'm having some problems with an API that returns some detailed information as JSON when serving HTTP 500. I want to read that data and evaluate it. Unfortunately I'm always getting nil.

It would be nice to return this data if it if can be parsed instead of just returning nil, if an HTTP error occurs between outside of 200 till 300.

Code=411 "length required"

hello,
i get error when using multipart-data

Error 411: Error Domain=NetworkingErrorDomain Code=411 "length required" UserInfo={NSLocalizedDescription=length required}

Clear image cache

Maybe something like:

public func removeImage(path: String)
public func removeImages()

The second method removes all the files, probably more convenient/necessary than the first one.

Custom Accept header

Right now there's no way to restrict a response type to let's say: "image/png"

Request html page?

I've checked readme file but it is still unclear for me - how to request just a page? for example, main page from "youtube.com"?

This library always tries to convert the data into json (and of course can't) and so it returns no data but error only. Yes, I tried advices from "Choosing a content type" but I still can't understand what I'm doing wrong

Escape parameters when using x-www-form-urlencoded

This is the recommended way of formatting the parameters when using x-www-form-urlencoded.

This is the default content type. Forms submitted with this content type must be encoded as follows:

1.- Control names and values are escaped. Space characters are replaced by +, and then reserved characters are escaped as described in [RFC1738], section 2.2: Non-alphanumeric characters are replaced by %HH, a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as "CR LF" pairs (i.e., %0D%0A).

2.-The control names/values are listed in the order they appear in the document. The name is separated from the value by = and name/value pairs are separated from each other by &.

Link

More info: http://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data

Improve network activity indicator handling

Right now when an operation starts the networking indicator will show, and when the operation ends if gets hidden.

This works well if there's only one networking operation at the time, but sucks when there are multiple running since the networking indicator will get hidden when one operation ends even though the other operation is still in progress.

Would be great to have a NetworkActivityIndicator queue, which managed this behaviour.

Request cancellation

This time by calling cancelRequest we cancel all requests that match our criteria (urls & http methods are equal). I'd consider using unique identifiers to distinguish requests. As an option, we might use taskIdentifier from NSURLSessionTask for this.

The updated version would work in the next way:

let networking = Networking(baseURL: "http://httpbin.org")
let taskId = networking.GET("/get") { JSON, error in
    // Cancelling a GET request returns an error with code -999 which means cancelled request
}

networking.cancel(taskId)

Fake a request with delay

At the moment all fake request return immediately. Would be great to have a delay in order to test UI responses and interactions such as viewing the loading indicator for a few seconds before seeing the data.

Support other Content-Types

Example:

let request = NSMutableURLRequest(URL: NSURL(string: "https://api.jottacloud.com/auth/v1/token")!)
request.HTTPMethod = "POST"
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let dataString = "grant_type=PASSWORD&client_id=\(self.clientID)&username=\(username)&password=\(password)".dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPBody = dataString
NSURLSession.sharedSession().dataTaskWithRequest(request) { data, _, error in......

Use Result enum instead of multiple optionals

Old

let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
networking.GET("/search", parameters: ["query": "cute cats"]) { JSON, headers, error in
    if let error = error {
        // return error
    } else {
        let convertedJSON = JSON as! [String : AnyObject]
        // process convertedJSON
    }
}

New

let networking = Networking(baseURL: "https://api-news.layervault.com/api/v2")
let requestID = networking.GET("/search", parameters: ["query": "cute cats"]) { result in
        switch result {
        case let .success(json, headers):
            // Headers included in response
        case let .error(json, headers, error):
            // Headers included in response
        }
}

Improve cancelled request logged error

Current:

========== Networking Error ==========

Error -999: Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://ddxxx.com/38bea9c8b75bfed1326f90c48675fce87dd04ae6/thumb/small, NSLocalizedDescription=cancelled, NSErrorFailingURLStringKey=https://ddxxx.com/38bea9c8b75bfed1326f90c48675fce87dd04ae6/thumb/small}

Request: <NSMutableURLRequest: 0x7fa8e27bc330> { URL: https://ddxxx.com/38bea9c8b75bfed1326f90c48675fce87dd04ae6/thumb/small }

================= ~ ==================

Should probably be something like:

========== Networking Error ==========

Cancelled request: GET https://ddxxx.com/38bea9c8b75bfed1326f90c48675fce87dd04ae6/thumb/small

================= ~ ==================

Request progress block

Haven't found data upload progress block in documentation. Is it planned or forgotten to appear in documentation?

Library looks simple and tasty! Thank you!

Recording sessions

A flag that turns on recording sessions. It will store the request details and response in a .plist that can be read afterwards to fake requests.

Response headers

Any possibility of returning response headers in the callback block?

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.