-
Notifications
You must be signed in to change notification settings - Fork 0
중규_네트워크 Endpoint 분석, Moya 네트워크 레이어 분석, Moya 네트워크 레이어 구현
joongkyu-park edited this page May 1, 2023
·
1 revision
-
Endpoint
- 서버와 클라이언트 간의 통신을 위한 API 요청이 가능한 주소를 나타내는 특정 URL 또는 URI
-
Endpoint 구성
- baseURL
- API Endpoint의 기본 URL.
모든 요청은 baseURL을 기반으로 한다. - ex) "https://api.example.com/users" 에서 baseURL은 "https://api.example.com/”
- API Endpoint의 기본 URL.
- path
- baseURL 이후에 따라오는 경로를 지정
- ex) “"https://api.example.com/users/1” 에서 path는 /users/1
- method
- HTTP 요청 방법을 지정. GET, POST, PUT, DELETE 등
- queryParameters
- URL에 추가되는 쿼리 매개변수를 지정.
쿼리 매개변수는 키와 값의 쌍으로 구성되며, 여러 개의 매개변수를 전달할 경우 "&"로 구분.
- URL에 추가되는 쿼리 매개변수를 지정.
- bodyParameters
- HTTP 요청에서 전송되는 데이터를 지정.
일반적으로 POST나 PUT 메소드에서 사용되며, JSON이나 Form Data 형식으로 전송될 수 있다.
- HTTP 요청에서 전송되는 데이터를 지정.
- headers
- HTTP 요청에서 사용되는 헤더를 지정.
일반적으로, 인증 토큰이나 언어, 콘텐츠 유형 등의 정보를 전송.
- HTTP 요청에서 사용되는 헤더를 지정.
- sampleData
- Endpoint 요청 시 반환되는 샘플 데이터를 지정.
API 문서 작성 또는 테스트 용도로 사용된다.
- Endpoint 요청 시 반환되는 샘플 데이터를 지정.
- baseURL
-
클라이언트 단에서는 이 Endpoint 요소들을 조합하여 request를 만들어서 서버와 HTTP 통신을 할 수 있다.
-
예시
// 참고: https://velog.io/@isouvezz/Swift-Network-Layer-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0#network-%EB%A0%88%EC%9D%B4%EC%96%B4-%EC%84%A4%EA%B3%84 enum HttpMethod: String { case get = "GET" case post = "POST" case put = "PUT" case delete = "DELETE" } // Endpoint 구성 요소 protocol Requestable { var baseURL: String { get } var path: String { get } var method: HttpMethod { get } var queryParameters: Encodable? { get } var bodyParameters: Encodable? { get } var headers: [String: String]? { get } var sampleData: Data? { get } } extension Requestable { func getUrlRequest() throws -> URLRequest { let url = try makeUrl() var urlRequest = URLRequest(url: url) urlRequest.httpMethod = method.rawValue if let bodyParameters = try bodyParameters?.toDicionary() { if !bodyParameters.isEmpty { urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: bodyParameters) } } headers?.forEach { key,value in urlRequest.setValue(value, forHTTPHeaderField: "\(key)") } return urlRequest } func makeUrl() throws -> URL { let fullPath = "\(baseURL)\(path)" guard var urlComponents = URLComponents(string: fullPath) else { throw NetworkError.componentsError} var urlQueryItems = [URLQueryItem]() if let queryParameters = try queryParameters?.toDicionary() { queryParameters.forEach { key,value in urlQueryItems.append(URLQueryItem(name: key, value: "\(value)")) } } urlComponents.queryItems = urlQueryItems.isEmpty ? nil : urlQueryItems guard let url = urlComponents.url else { throw NetworkError.componentsError } return url } }
-
NetworkAPI
import Foundation struct NetworkAPI { private let provider = NetworkProvider<NetworkService>() } // MARK: - API 요청 함수들 extension NetworkAPI { func getMovies(category: String, completionHandler: @escaping (MovieResponse?, Error?) -> Void) { do { let request = try provider.request(.getMovies(category: category)) let task = URLSession.testSession.dataTask(with: request) { (data, response, error) in guard let data = data else { completionHandler(nil, NetworkError.nilData) return } guard let response = response as? HTTPURLResponse else { completionHandler(nil, NetworkError.invalidServerResponse) return } do { let result = try self.judgeStatus(by: response.statusCode, data, type: MovieResponse.self) completionHandler(result, nil) } catch let error { completionHandler(nil, error) } } task.resume() } catch let error { completionHandler(nil, error) } } // 중략 } extension NetworkAPI { private func judgeStatus<T: Decodable>(by statusCode: Int, _ data: Data, type: T.Type) throws -> T { switch statusCode { case 200: return try decodeData(from: data, to: type) case 400..<500: throw NetworkError.requestError(statusCode) case 500: throw NetworkError.serverError(statusCode) default: throw NetworkError.networkFailError(statusCode) } } private func decodeData<T: Decodable>(from data: Data, to type: T.Type) throws -> T { guard let decodedData = try? JSONDecoder().decode(T.self, from: data) else { throw NetworkError.decodeError(toType: T.self) } return decodedData } }
-
NetworkProvider
import Foundation struct NetworkProvider<Target: TargetType> { func request(_ target: Target) throws -> URLRequest { // MARK: - Path let path = target.baseURLPath + target.path guard let urlComponents = URLComponents(string: path) else { throw NetworkError.invalidURLComponents(path) } // MARK: - URL var url: URL? let task = target.task switch task { case .requestPlain, .requestJSONEncodable: url = urlComponents.url } guard let url = url else { throw NetworkError.invalidURLString } // MARK: - Ruqest var request = URLRequest(url: url) switch task { case .requestPlain: break case .requestJSONEncodable(let body): let bodyEncoded = try JSONEncoder().encode(body) request.httpBody = bodyEncoded } // MARK: - Header request.httpMethod = target.method.rawValue if let headerField = target.headers { _ = headerField.map { (key, value) in request.addValue(value, forHTTPHeaderField: key) } } return request } }
-
TargetType
import Foundation protocol TargetType { var baseURLPath: String { get } var path: String { get } var method: HTTPMethod { get } var task: NetworkTask { get } var headers: [String: String]? { get } }
-
NetworkService
import Foundation enum NetworkService { case getMovies(category: String) case createFavorite(id: String, name: String) case deleteFavorite(id: String) // 중략 } extension NetworkService: TargetType { var baseURLPath: String { return "https://api.example.com/" } var path: String { switch self { case .getMovies: return "/movies" case .createFavorite: return "/favorite" case .deleteFavorite: return "/favorite" } } var method: HTTPMethod { switch self { case .getMovies: return .get case .createFavorite: return .post case .deleteFavorite: return .delete } } var task: NetworkTask { switch self { case .getMovies, .deleteFavorite: return .requestPlain case .createFavorite(let id, let name): let item = FavoriteRequestItem(id: id, name: name) return .requestJSONEncodable(body: item) } } var headers: [String: String]? { switch self { case .getMovies, .createFavorite: return nil case .deleteFavorite(let id): return ["id": id] } } }
-
NetworkService를 위한 요소들(HTTPMethod, NetworkTask …)
import Foundation struct HTTPMethod: RawRepresentable, Equatable, Hashable { static let get = HTTPMethod(rawValue: "GET") static let post = HTTPMethod(rawValue: "POST") static let patch = HTTPMethod(rawValue: "PATCH") static let delete = HTTPMethod(rawValue: "DELETE") let rawValue: String init(rawValue: String) { self.rawValue = rawValue } } enum NetworkTask { case requestPlain case requestJSONEncodable(body: Encodable) } // 중략