GET PARAM에 넣어야할 값은 아래와 같습니다. key = 서비스가입 후 받은 OPEN API KEY를 넣어주세요. apiCode = api명령인데, 현재는 상품 검색을 할 것이므로, 'ProductSearch'를 그대로 두세요. keyword = 검색할 명령어를 입력해 주세요.
keyword'양말'로 입력하고 브라우저 주소창에 넣으면 xml 응답값을 받을 수 있습니다. 11번가에 등록되는 상품들은 매일매일 다르므로 호출할때마다 다른 응답을 받이실거에요.
브라우저에서 사용하면 편하지만, 우리는 서버환경에서 받아 대체로 가공해서 사용해야하므로, 서버 언어로 실행이 필요합니다. node.js를 사용해 서버환경에서 구현해 보겠습니다.
var http = require('http');
function get_search(rest_api_key, keyword, callback){
var options = {
hostname: 'openapi.11st.co.kr',
path: `/openapi/OpenApiService.tmall?apiCode=ProductSearch&key=${rest_api_key}&keyword=${encodeURI(keyword)}`
};
function handleResponse(response) {
var serverData = '';
response.on('data', function (chunk) {
serverData += chunk;
});
response.on('end', function () {
// console.log("received server data:");
// console.log(serverData);
callback(serverData);
});
}
http.request(options, function(response){
handleResponse(response);
}).end();
}
get_search([rest api key 입력], "양말", res=>{
console.log("응답 =>", res);
});
html 은 일반 채팅과 같은 TCP/IP 프로토콜을 사용하지만, 클라이언에서 서버로 요청을 보내면, 요청에 대한 처리 후 접속을 끊어 버립니다. 그런 이유로 단방향 통신이라하며, 서버에서 원할 때 클라이언트로 메시지를 보낼 방법이 없습니다.
하지만, HTML5에서 웹소켓 개념이 추가되며, 이런 문제를 해결하였습니다. node.js에서 잘 알려진 웹소켓 지원 모듈은 ws 와 socket.io 입니다. 둘다 websocket을 이용할 수 있다는 점은 같지만, 특징이 뚜렸하기 때문에, 사용목적이 달라질 수 있습니다.
ws : 표준 웹소켓을 구현하였습니다. 그래서, 웹 브라우저, 게임을 포함한 앱, 윈도우 프로그램 모든 곳에서 사용할 수 있습니다. 하지만, 구형 웹브라우저나, 웹소켓이 모두 구현되지 않은 곳에서는 잘 동작하지 않을 수 있습니다.
socket.io : 브라우저만을 위한 특화된 방식으로 모듈이 구현되어 있습니다. 그래서 표준 웹소켓이 아니므로, 웹에서만 사용이 가능합니다. 하지만, 웹소켓을 지원하지 않는 브라우저는 polling을 이용해 연결합니다. 즉, 99.99% 브라우저에서 동작합니다.
이 부분은 따로 설명하지 않습니다. ws 사용법을 집중적으로 다루기 위함 입니다. express를 사용해 본 경험이 없으시다면, 참고 문서의 express 구축 ( 링크 ) 페이지를 따라 구축해 주시면 될 것 같습니다.
2. socek.io를 이용한 route 구축.
- socket.io 모듈을 설치 합니다.
$ npm i socket.io
- 웹소켓을 구현 합니다.
그리고 웹소켓을 처리할 모듈파일 webSocket.js 을 적당한 위치에 만들어 줍니다. 모듈 파일을 생성했다면, 아래와 같이 소스를 만들어 줍니다. (각 소스에 대한 설명은 주석을 달아 두었습니다.). socket.io의 특징은 connect, disconnect, error외에 이벤트를 직접 정의 한다는 것 입니다. 즉 어디로 받을 것인지 선택할 수 있으며, 아래소스에서는 ms라는 이벤트를 추가하였습니다.
const SocketIO = require("socket.io");
module.exports = function ( _server ) {
// 소켓 io를 처리할 객체를 생성합니다.
const io = SocketIO( _server, {path: '/socket.io'} );
// 접속 처리 및 해당 클라이언트에 대한 모든 처리를 합니다.
io.on( "connection", function( socket ){
// 접속한 정보를 표시합니다.
const req = socket.request;
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
console.log( ip + "의 새로운 유저가 접속하였습니다." );
// 접속이 끊어진 경우 처리를 하는 콜백 입니다.
socket.on( "disconnect", function( ){
console.log("접속을 해제 하였습니다.");
});
// 오류가 난 경우 처리를 하는 콜백 입니다.
socket.on( "error", function( error ){
console.log( "error:" + error );
});
// 일반 메시지를 받은 경우의 처리 입니다. (ms 라는 이벤트로 클라이언트가 보낼 때 마다 이 메소드가 호출 됩니다.)
socket.on( "ms", function( data ){
// 클라이언트로 부터 받은 메시지를 표시합니다.
console.log( "클라이언트로 부터 받은 메시지 : " + data );
// 클라이언트에 구현된 echo 이벤트로 메시지를 보내어 줍니다.
socket.emit( "echo", data );
});
});
}
- 구현한 웹소켓을 연결합니다.
그런다음 모듈을 http 서버에 연결해 줍니다. (express 같은 경우 listen을 통해 만들어진 서버, http의 경우 createServer 메소드로 만들어진 서버). 위에 링크된 express 구축을 따라했다면, /bin/www 파일속에 server 객체가 정의 되어 있습니다. 적당한 위치에 아래와 같이 웹소켓을 연결하도록 합니다.
const webSocket = require( "../routes/webSocket" );
webSocket( server );
3. 브라우저를 통해 웹소켓 서버에 접속해 보기.
- 지금까지 구현한 서버 실행.
$ npm start
- 클라이언트 만들고 호출 하기.
테스트할 웹브라우저에 아래 소스를 추가합니다. 웹에서 우리가 만든적 없는 js 파일 "/socket.io/socket.io.js" 파일을 추가하는데, express 서버에 socket.io를 구현하였다면, 자동으로 추가되어 다운로드 받을 수 있습니다. 그리고, 서버와 마찬가지로, 받는 이벤트명을 마음대로 정할 수 있습니다. 특징은 접속을 따로 하거나, 연결을 따로 하지 않습니다. 끊어지면 바로 자동으로 새 접속을 시도합니다. (언제나 연결을 유지 합니다.)
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script src="/socket.io/socket.io.js"></script>
<script>
// 웹소켓 서버를 설정합니다.
var socket = io.connect( "http://localhost:3000" , {path: "/socket.io"} );
socket.on( 'echo', function( data ){
console.log( data );
writeToScreen( "받은메시지:" + data )
});
// 서버로 데이터를 보내기 위한 메소드 입니다.
function sendMessage( )
{
// 서버에 구현된 ms 이벤트로 메시지를 보냅니다.
mss = document.getElementById( "message" ).value;
socket.emit( "ms", mss );
}
// 받은 메시지를 출력합니다.
function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
</script>
<h2>소켓 io 동작을 테스트 하기 위한 node js 입니다.</h2>
<form>
<input id="message" /> <button type="button" onclick="sendMessage();" >전송</button><br>
</form>
<div id="output"></div>
- 브라우저 호출.
브라우저에서 위의 소스를 실행하면, node.js에 아래와 같은 메시지를 볼 수 있습니다. (자동으로 바로연결됨)
그간의 html 통신은 요청에 대한 처리를 한 후 응답을 돌려주는 방식으로 동작했습니다. 우리가 흔히 챗팅 프로그램에서 사용하는 포로토콜인 TCP/IP를 사용했지만, [연결->요청에대한처리->응답->연결끊기] 사이클을 반복하며, 연결을 계속 끊기 때문에, 클라이언트의 요청은 언제든지 전달 할 수 있지만, 서버의 메시지는 클라이언트로 전달할 수 없었습니다.
그래서 나온 것이 웹소켓 이며, 서버의 구현도 간단하며 (직접 구현해도 될 정도), 클라이언트 또한 매우 간결하며, 요즘은 대부분은 웹브라우저에서 지원을 해 줍니다. 이 페이지는 ws 모듈을 이용해 웹브라우저와 node.js간의 간단한 통신을 구현한 예제를 설명하고 있습니다. 간단하지만, 이를 바탕으로 원하는 채팅 모듈을 구현할 수 있으리라 생각이 됩니다.
본 문서를 보려면, node.js 에서 express 서버의 구축 과정을 알고 있어야 합니다. (물론 아래에 관련 링크를 제공합니다.)
이 부분은 따로 설명하지 않습니다. ws 사용법을 집중적으로 다루기 위함 입니다. express를 사용해 본 경험이 없으시다면, 참고 문서의 express 구축 ( 링크 ) 페이지를 따라 구축해 주시면 될 것 같습니다.
2. ws를 이용한 route구축.
ws 모듈을 설치 합니다.
$ npm i ws
그리고 웹 소켓 통신을 처리할 모듈을 구현할 webSocket.js 파일을 만들어 줍니다.
모듈파일을 생성했다면, 아래와 같이 소스를 만들어 줍니다. (각 소스에 대한 설명은 주석을 달아 두었습니다.)
const wsModlue = require( "ws" );
module.exports = function( _server )
{
// 웹소켓 서버를 생성합니다.
const wss = new wsModlue.Server( {server:_server} );
// 클리이언트가 접속했을 때 처리하는 이벤트 메소드를 연결합니다.
wss.on( 'connection', function( ws, req ){
// 사용자의 ip를 파악합니다.
let ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
console.log( ip + "아이피의 클라이언트로 부터 접속 요청이 있었습니다." );
// 메시지를 받은 경우 호출되는 이벤트 메소드 입니다.
ws.on('message', function( message ){
// 받은 메시지를 출력합니다.
console.log( ip + "로 부터 받은 메시지 : " + message );
// 클라이언트에 받은 메시지를 그대로 보내, 통신이 잘되고 있는지 확인합니다.
ws.send( "echo:" + message );
});
// 오류가 발생한 경우 호출되는 이벤트 메소드 입니다.
ws.on('error', function(error){
console.log( ip + "클라이언트와 연결중 오류 발생:" + error );
})
// 접속이 종료되면, 호출되는 이벤트 메소드 입니다.
ws.on('close', function(){
console.log( ip + "클라이언트와 접속이 끊어 졌습니다." );
})
});
}
그런 다음 모듈을 http 서버에 연결해 줍니다. (express같은 경우 listen을 통해 만들어진 서버, http의 경우 createServer 메소드로 만들어진 서버). 위에 링크된 express 구축을 따라 했다면, /bin/www 파일 속에 server 객체가 정의 되어 있습니다. 적당한 위치에 아래와 같이 웹소켓을 연결하도록 합니다.
const webSocket = require("../routes/webSocket");
webSocket( server );
웹소켓은 익스프레스 서버와 port를 공유하므로, 따로 포트를 지정할 필요가 없습니다.
3. 브라우저를 통해 웹소켓 서버에 접속해 보기.
브라우저에서 웹소켓 이용 방법은 위의 참고 문서 [javascript 웹소켓 클라이언트 (링크)] 문서를 읽어 주세요. 이 페이지는 node js를 통한 웹소켓 서버 구축만 집중적으로 설명을 합니다.
웹소켓을 테스트하기 위한 웹 브라우저는 따로 만들지 websocket공식 사이트에 구현된 챗팅 클라이언트 페이지를 이용합니다. 웹소켓 기능을 구현할 때 일단 websocket 공식 사이트에 구헌된 챗팅 클라이언트 페이지를 이용해 구축하고, 클라이언트 부분은 이렇게 완성된 웹소켓 서버와 연결하는 식으로 구축합니다. 2가지를 한 꺼번에 구축하면, 오류 발생시 어느쪽이 문제인지 알 기 힘듭니다. 이렇게 단계별로 만든다면, 약간은 쉽게 구현이 가능합니다.
먼저 지금 까지 구현 한 서버를 동작 시켜 줍니다.
$ npm start
그 다음, 클라이언트로 이용할 https://www.websocket.org/echo.html 페이지로 접속합니다. 그러면 아래와 같은 화면을 볼 수 있습니다. 여기서, 로케이션 창에 주소를 입력하고 connect 버튼을 누릅니다. 이라고 입력 합니다. 위의 참고 문서 [express 구축] 을 보고 서버를 구축했다면, 주소는 ws://localhost:3000 이 됩니다.
그럼 아래와 같이 접속이 됩니다.
서버 또한 클라이언트로 부터 연결이 있었음을 알 수 있는 로그를 표시합니다.
다음 웹브라우저에서 서버로 메시지를 보내 보겠습니다. Message 항목에 적당한 메시지를 입력하고, Send 버튼을 누릅니다.
신규앱을 등록하는 폼이 나타나면 아래와 같이 입력해 줍니다. 이름은 앱스토어 내에서 고유해야 합니다. 이미 사용중인 경우, 오류가 납니다. SKU는 앱의 상점을 위한 아이디인데, 적당히 고유한 값을 주면됩니다. 다 입력했으면 생성 버튼을 눌러 주도록 합니다.
앱 이 생성되고, 앱이 표시 되면, 메뉴의 [앱 내 추가 기능]을 선택해 주도록 합니다.
그럼 앱내 구입 항목을 선택할 수 있는데 여기서 + 버튼 눌러 소모성 상품을 추가하도록 합니다.
앱 내 생성하려는 구입 선택항 목에서, 소모품을 선택하고 생성 버튼을 눌러 주도록 합니다.
그 다음 상품을 넣는 창에서 각각 적당한 값을 넣으면 되지만, 제품 ID는 각 상품마다 고유한 값을 잘 넣어 줘야 합니다.
그럼 등록되었지만, [메타 데이터 누락됨] 이라고 나올 수 있는데, 이 것은 우리가 심사를 위한 데이터를 넣지 않아 생기는 일이고, 테스트를 위한 것은 여기까지 입력하면됩니다.
저는 아래와 같이 3개의 소모성 아이템을 구매 했습니다.
앱은 현재 메타 데이터가 누락되어 있는데, 메타데이터를 설정된 뒤, 다음과 같이 앱내 구입으로 선택되어야 합니다.
3. 결제 옵저버.
swift에서 옵저버는 멤버 프로퍼티의 값이 변할 때, 알려주는 일종의 콜백 입니다. 결제가 시작될 때, 거절 될때등, 결제 상태를 확인해 이를 처리할 옵저버가 필요합니다.
옵저버는 SKPaymentTransactionObserver 프로토콜(c#의 인터페이스)을 상속해 구현해야 합니다. 코드는 대략 아래와 같습니다.
class StoreObserver: NSObject, SKPaymentTransactionObserver {
// 멤버 변수들.
var purchased = [SKPaymentTransaction]();
override init() {
super.init()
// 생성자를 위한 초기화 메소드.
}
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions
{
switch transaction.transactionState
{
// 결제가 진행 중인 경우.
case .purchasing :
print("결제가 진행되고 있습니다.");
break;
// 결제 창을 띄우는 데 실패했습니다.
case .deferred:
print("아이폰의 잠기는 등의 이유로 결제 창을 띄우지 못했습니다.");
SKPaymentQueue.default().finishTransaction( transaction );
break;
// 결제를 성공한 경우.
case .purchased:
print("결제를 성공하였습니다.");
handlePurchased( transaction );
SKPaymentQueue.default().finishTransaction( transaction );
break;
// 결제를 실패한 경우.
case .failed:
print("결제를 실패하였습니다.");
SKPaymentQueue.default().finishTransaction( transaction );
break;
// 결제 검증을 하였습니다.
case .restored:
print("상품 검증을 하였습니다.");
SKPaymentQueue.default().finishTransaction( transaction );
break;
default :
print("알수 없는 오류를 만났습니다.")
SKPaymentQueue.default().finishTransaction( transaction );
break;
}
}
}
func handlePurchased( _ transaction : SKPaymentTransaction ) {
purchased.append(transaction)
SKPaymentQueue.default().restoreCompletedTransactions();
print( "영수증 주소 : \(Bundle.main.appStoreReceiptURL)" );
let receiptData = NSData( contentsOf: Bundle.main.appStoreReceiptURL! );
print(receiptData)
let receiptString = receiptData!.base64EncodedString(options: NSData.Base64EncodingOptions());
print ( "구매 성공 트랜젝션 아이디 : \(transaction.transactionIdentifier!)" );
print ( "상품 아이디 : \(transaction.payment.productIdentifier)" );
print ( "구매 영수증 : \(receiptString)" );
// 결제를 마무리 하도록 합니다.
SKPaymentQueue.default().finishTransaction( transaction );
}
}
대게의 코드는 공식 문서를 통해 쉽게 알 수 있지만 중요한 부분은 영수증을 받는 부분 입니다. 이 부분은 크게 언급이 공식 문서에 없어, 애를 먹었는데, Bundle.main.appStoreReceiptURL 값에 영수증 파일명이 들어 있습니다. 이것을 NSData를 이용해 값을 가지고 오고, Base64 인코딩을 해 사용하면됩니다.
결제를 할 때마다 transaction 이 생성되는 데, 실패하던, 걸절하던, 꼭 finishTransaction 처리를 해 줘야 합니다. 그렇지 않으면, 껐다 키면 다시 트랜젝션값들이 계속 추가 됩니다. 이 것은 앱이 비정상적으로 종료되었을 때도, 처리를 하기 위함으로 보입니다.
4. 상품 리스트 가지고 오기 및 구매 처리.
결제는 대체로 뷰에서 일어 나므로, viewController에서 처리하는 것이 좋습니다.
처음 상품 리스트를 가지고 오는 부분이 있습니다. 이때, 위에서 등록한 상품의 ID들을 적어 주면됩니다. 코드는 아래와 같습니다.
import UIKit
import StoreKit
class ViewController: UIViewController, SKProductsRequestDelegate {
// 멤버 변수들.
let iapObserver = StoreObserver()
var productRequest : SKProductsRequest?
// 입력 받은 상풉 정보들을 저장하는 변수들.
var validProductArray = [SKProduct]();
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// 페이먼트 관련 설정을 해 주도록 합니다.
SKPaymentQueue.default().add(iapObserver)
// 상품 정보를 가지고 오는 리퀘스트를 설정하도록 합니다.
let pIDs = Set(["gold500","gold100"]);
productRequest = SKProductsRequest( productIdentifiers: pIDs );
productRequest!.delegate = self;
// 앱스토어 상품 정보를 요청합니다.
productRequest!.start();
}
// 구매 진행 가능 여부를 확인합니다.
@IBAction func checkPaymentAble(_ sender: Any) {
if( SKPaymentQueue.canMakePayments() )
{
print("결제 요청이 가능합니다.");
}else
{
print("결제 요청이 불가능합니다.");
}
}
@IBAction func tryPaymentQueue(_ sender: Any) {
print( "골드 100개 결제 할거에요." )
let payment = SKMutablePayment( product: validProductArray[0] );
SKPaymentQueue.default().add(payment)
print( "변화가 있나요?" )
}
// 상품 정보를 받은 후 처리 메소드 입니다.
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("상품갯수 \(response.products.count)")
print("확인하지 못한 상품 갯수 \(response.invalidProductIdentifiers.count)")
for product in response.products
{
print("이름:\(product.localizedTitle)\n가격:\(product.price)")
validProductArray.append(product)
}
}
}
이 것만으로 결제는 코드는 끝 입니다. 실제 결제를 해 볼때는 아이폰에서, 애플 아이디를 로그아웃하는 것이 좋습니다.
5. 검증 서버. (php)
원래 node js만 구현하려고 했으나, 잘 되지 않아 php로 먼저 결제 서버를 구현하였고, 그것을 바탕으로 node.js를 구현하였습니다.
npm의 외부 모듈 설치는 간단합니다. 터미널을 열어 일단 모듈을 읽어드릴, js 파일이 있는 폴더로 이동합니다. 저는 '/test' 폴더로 이동 이곳에 npm을 이용 외부 모듈을 설치했습니다. 저는 맥 사용자 이므로, 맥의 터미널을 사용했지만, 윈도우의 경우 [명령 프롬프트]를 이용하시면됩니다. 이 포스트는 윈도우용 설명을 보고 만들었습니다. 즉, 완전히 방법이 똑 같습니다.
아래 단 두줄로 간단히 nconf 라는 외부 모듈이 설치되었습니다. cd /test npm install nconf
설치를 마치면, '/test' 폴더의 하위에 'node_moduels' 라는 폴더가 생기가, nconf라는 외부 모듈이 다운 받아 졌음을 확인할 수 있습니다. 나머지 폴더들은 새로 생긴 것들은 인데, nconf 모듈 실행에 필요한 모듈들로 생각됩니다.