티스토리 뷰

iOS

[iOS] URLSession의 Content-Type

희철 2022. 11. 7. 17:31

로그인 프로젝트에서 Alamofire를 이용해 데이터를 받아왔었는데 URLSession을 써보고 싶어져서 바꿔보기로 했다.

 

회원가입, 로그인, 프로필정보 

이렇게 세 가지의 네트워킹을 하고 있는 상태다.

 

회원가입에서는 이메일, 이름, 패스워드 세 가지의 파라미터를 받고있고, 로그인에서는 이메일과 패스워드, 그리고 프로필정보에서는 파라미터를 받고 있지 않는다.

 

다시 말해서, 회원가입과 로그인은 파라미터를 같이 보내야하고 프로필정보는 파라미터를 보낼 필요가 없다는 말이다(헤더로 토큰만)

 

프로필정보를 받아오기까지는 문제가 전혀 없었다.

 

    static func requestProfile(completionHandler: @escaping ((Profile?) -> ())) {
        
        let api = SesacAPI.profile
        
        let url = api.url
        let session = URLSession(configuration: .default)
        var urlRequest = URLRequest(url: url)
        
        urlRequest.addValue("Bearer \(UserDefaultsManager.fetchAccessToken())", forHTTPHeaderField: "Authorization")
        
        session.dataTask(with: urlRequest) { data, response, error in
            if error != nil {
                print("error") //일단 임시
                return
            }
            print((response as? HTTPURLResponse)?.statusCode)
            if let data = data {
                do {
                    let decodedData = try JSONDecoder().decode(Profile.self, from: data)
                    completionHandler(decodedData)
                } catch {
                    print("디코딩에러") //임시
                }
            }
        }.resume()
    }

프로필정보를 받아오기 위해서는 헤더로 토큰만 보내주면된다.

 

보내줄 파라미터가 없기 때문에 URLRequest에 addValue로 저장되어 있는 토큰만 보내고 dataTask를 통해 데이터를 쉽게 받아올 수 있었다.

 

문제는 파라미터가 있는 경우였다.

 

기존 파라미터의 타입은 [String: String]형태로 설정해둔 상태였다.

    var parameters: [String: String]? {
        switch self {
        case .signUp(let username, let email, let password):
            return [
                "userName": username,
                "email": email,
                "password": password
            ]
        case .signIn(let email, let password):
            return [
                "email": email,
                "password": password
            ]
        case .profile:
            return nil
        }
    }

일반적으로 서버로 데이터를 보낼 때는 json형태로 보내는게 일반적이다.

 

그래서 처음에는 JSONSerialization.data를 이용해 파라미터를 JSON형태로 만들어서 body로 넣어주었다.

guard let requestBody = try? JSONSerialization.data(withJSONObject: parameters) else { return }

 

그리고 이때, 헤더로는 Content-Type을 인섬니아에서 사용했던 것처럼 application/x-www-form-urlencoded를 사용했다.

request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

 

이렇게 작성하고 진행했더니 JSONDecoder부분에서 계속 에러가 캐치됐다.

        let api = SesacAPI.signIn(email: email, password: password)

        guard let parameters = api.parameters else { print("QKrclspd")
            return }
        
        do {
            let requestBody = try JSONSerialization.data(withJSONObject: parameters)
            
            var request = URLRequest(url: api.url)
            request.httpBody = requestBody
            request.httpMethod = "POST"
            request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
            
            let session = URLSession(configuration: .default)
            session.dataTask(with: request) { data, response, error in
                
                guard error == nil else {
                    //nil이 아닌경우
                    print("error")
                    return
                }
                
                guard let data = data, let response = response as? HTTPURLResponse else { return }
                print(response.statusCode)
                print(response)
                let stringData = String(data: data, encoding: .utf8)
                print(stringData)
                do {
                    let decodedData = try JSONDecoder().decode(SignIn.self, from: data)
                    print("하하", decodedData.token)
                    UserDefaultsManager.setAccessToken(token: decodedData.token)
                    completionHandler(true)
                } catch {
                    print("에러에러")
                }
            }.resume()
        } catch {
            print("dpfj")
        }

    }

String(data: data, encoding:  .utf8)을 이용해 출력해봤더니 잘못된 이메일과 비밀번호라는 정보가 떴다.

 

하지만 인섬니아에서 같은 이메일과 비밀번호를 시도해보았을 때는 정상적으로 토큰값을 받아올 수 있었따.

 

 

문제는 헤더였다.

 

 

JSONSerialization을 이용하면 JSON형태로 변환이 될텐데 이 데이터를 파라미터로 전달하기 위해서는 Content-Type을 바꿔줘야한다.

request.addValue("application/json", forHTTPHeaderField: "Content-Type")

해결하는데 굉장히 오래걸렸지만 위처럼 바꾸기만 하면 정상적으로 데이터를 받아올 수 있따.

 

JSON형식으로 바꿔줬으니 그에 알맞는 Content-Type을 사용해야한다.

 

 

두 Content-Type의 다른 점은 데이터 형태이다.

 

만약 x-www-form-urlencoded로 보내는 경우라면 데이터는 "key=value&key1=value1"의 형태를 갖고 있어야한다.

 

그래서 처음과 같이 x-www-form-urlencoded로 보낸다면 [String:String]타입을 위에 보이는 &로 이루어진 형태로 바꿔줘야한다.

 

 

 

바꿔보았음

 

위에서 말했듯이 "key=value&key=value" 형태로 바꿔야한다고 했었다.

let pa = parameters.map{ "\($0.key)=\($0.value)" }.joined(separator: "&")

 

이제 parameter는 문자열형태로 바뀌었다.

 

이제 httpbody로 보내기위해 데이터 형태로 만들어준 뒤에 네트워크 통신을 진행하면 정상적으로 토큰을 받을 수 있다.

request.httpBody = pa.data(using: .utf8)

 

전체코드(정리안해서 지저분)

    func requestSignIn(email: String, password: String, completionHandler: @escaping ((Bool) -> ())) {

        let api = SesacAPI.signIn(email: email, password: password)
        
        guard let parameters = api.parameters else { print("QKrclspd") //[String: String]형태
            return }
//        let pa = parameters.map{ "\($0.key)=\($0.value)" }.joined(separator: "&") => "key=value&key=value형태"

        do {
            let requestBody = try JSONSerialization.data(withJSONObject: parameters) //[String: String]형태일때
            
            var request = URLRequest(url: api.url)
            
            request.httpBody = requestBody
//            request.httpBody = pa.data(using: .utf8) => urlencoded사용할때
            
            request.httpMethod = "POST"
            
            request.addValue("application/json", forHTTPHeaderField: "Content-Type") //[String: String]형태를 JSONSerialization을 ㅇ이용해서 변환 뒤에 사용할 때
            
//            request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
            
            let session = URLSession(configuration: .default)
            session.dataTask(with: request) { data, response, error in
                
                guard error == nil else {
                    print("error")
                    return
                }
                
                guard let data = data, let response = response as? HTTPURLResponse else { return }
                let stringData = String(data: data, encoding: .utf8)
                print(stringData)
                do {
                    let decodedData = try JSONDecoder().decode(SignIn.self, from: data)
                    UserDefaultsManager.setAccessToken(token: decodedData.token)
                    completionHandler(true)
                } catch {
                    print("에러에러")
                }
            }.resume()
        } catch {
            print("dpfj")
        }
    }

 

 

내가 기억하려고 정리한거라 코드도 지저분하고 정보도 불친절함.

 

Content-Type이 application/x-www-form-urlencoded이라면 "key=value&key=value" 형태로 보내고 JSONSerialization없이 body에 넣기

 

Content-Type이 application/json이라면 JSONSerialization을 이용해 json형태로 만들어 보내기

 

댓글
최근에 올라온 글
Total
Today
Yesterday