학교에서 배운 JSP 관련 내용을 정리해둡니다.


본 내용은 mysql을 이용하는 db 연결부를 사용하기 편하도록 클래스로 분리하여둔 것 입니다. 사용된 외부 라이브러리들은 아래와 같습니다.


JSON-Simple-1.1.1

mysql-connector-java-5.1.24


본 내용은 이전 글( [JSP 1.x] Reflection Methods를 이용한 API구현 )과 이어집니다. 

관련된 모든 소스들은 깃허브에 업로드 되어있습니다.



목차 


1. 공용 변수 클래스 만들기

2. 공용변수 클래스를 이용해 DB Connector만들기 

3. DBConnector클래스와 API의 연동




1. 공용 변수 클래스 만들기 


API를 만들고 나서는 데이터베이스와 연결하는 부분이 필요했다. 매 API서블릿 마다 DB connector부를 만들 수는 없었기에 별도로 분리하려고 했다. 

클래스로 분리하기 먼저, JSP의 사용법을 잘 모르기... 때문에 공용 변수를 관리할 클래스를 만들어 두어야 할 필요성이 있다고 생각했다. 프로젝트를 진행하면서 connection string이외에도 다양한 값들을 공통으로 사용하게 될지 모르겠다고 생각했기 때문이다. 그 클래스는 아래와 같이 구현했다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package util;
 
/*
 * 프로젝트 공통 변수들을 Singleton으로 관리하는 클래스
 * 
 * @author      KYEONGSOO YOO
 * @Date        2018-06-04
 * */
public class CommonVariable {
    // ================================================================================
    // Properties
    // ================================================================================
    private static CommonVariable _instance = new CommonVariable();     // 실제 인스턴스
    private String jdbcDriverClassName;                                 // 디비 드라이버 명
    private String jdbcConnectionString;                                // 디비 커넥션 스트링
    private String jdbcUserAccount;                                     // 디비 사용자 명
    private String jdbcUserPassword;                                    // 디비 사용자 비밀번호
 
    // 디비 변수 관련 열거형
    public enum CV_DB_VARIABLE {
        DRIVER_NAME, CONNECTION_STRING, USER_ACCOUNT, USER_PASSWORD
    }
 
    // ================================================================================
    // Constructor
    // ================================================================================
    private CommonVariable() {
        jdbcDriverClassName = "com.mysql.jdbc.Driver";
        jdbcConnectionString = "jdbc:mysql://localhost:3306/whistle";
        jdbcUserAccount = "root";
        jdbcUserPassword = "1111";
    }
 
    // ================================================================================
    // Methods
    // ================================================================================
    /*
     * 공통 변수 클래스의 인스턴스를 가져온다. (본 클래스는 따로 Constructor를 실행하지 않음)
     * 
     * @author                  KYEONGSOO YOO
     * 
     * @return  Class           현재 클래스의 인스턴스 (singleton)
     */
    public static synchronized CommonVariable getInstance() {
        return _instance;
    }
 
    /*
     * 데이터베이스에 관련된 항목들만 가져온다
     * 
     * @param   type            가져올 항목의 열거형 코드
     * @return  String          각 코드에 맞는 private 변수들 
     */
    public String getDBInfo(CV_DB_VARIABLE type) {
        String ret;
        switch (type) {
        case DRIVER_NAME:
            ret = jdbcDriverClassName;
            break;
        case CONNECTION_STRING:
            ret = jdbcConnectionString;
            break;
        case USER_ACCOUNT:
            ret = jdbcUserAccount;
            break;
        case USER_PASSWORD:
            ret = jdbcUserPassword;
            break;
        default:
            ret = "";
            break;
        }
 
        return ret;
    }
}
cs




2. 공용변수 클래스를 이용해 DB Connector만들기 


위 클래스를 이용해 , 본격적으로 DB CONNECTOR를 만들기로 했다. 구성은 아래와 같다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package util;
 
import java.sql.*;
import org.json.simple.*;
import org.json.simple.parser.*;
 
/*
 * 데이터베이스와의 연결을 위한 클래스 
 * 
 * @author          KYEONGSOO YOO
 * @Date            2018-06-05
 * */
public class DBConnector {
    // ================================================================================
    // Properties
    // ================================================================================
    private CommonVariable _cv;
    
    // ================================================================================
    // Constructor
    // ================================================================================
    public DBConnector() {
        super();
        _cv = CommonVariable.getInstance();
    }
    
    // ================================================================================
    // private Methods
    // ================================================================================
    private Connection connect() throws ClassNotFoundException, SQLException {
        Class.forName(_cv.getDBInfo(CommonVariable.CV_DB_VARIABLE.DRIVER_NAME));
        String sConn = _cv.getDBInfo(CommonVariable.CV_DB_VARIABLE.CONNECTION_STRING);
        String sAcc = _cv.getDBInfo(CommonVariable.CV_DB_VARIABLE.USER_ACCOUNT);
        String sPw = _cv.getDBInfo(CommonVariable.CV_DB_VARIABLE.USER_PASSWORD);
        Connection conn = DriverManager.getConnection(sConn, sAcc, sPw);
        return conn;
    }
 
    // ================================================================================
    // public Methods
    // ================================================================================
    
    /*
     * 디비에 인서트 / 업데이트를 진행할때 사용할 함수. 
     * 쿼리 자체를 트랜잭션을 짤 수 없어서 실제 문제 없이 처리되었을때만 commit되도록 처리하였다. 
     * 
     * @author              KYEONGSOO YOO 
     * @param   query       업데이트에 사용할 쿼리 
     * @return  JSONObject  affectedRow라는 변수를 담고있다. 실제 영향을 미친 row 수 이다. 
     * */
    public JSONObject update(String query) throws ClassNotFoundException, SQLException {
        Connection conn = null;
        Statement stmt = null;
        int affectedRowCnt = 0;
        JSONObject ret = new JSONObject();
        try {
            conn = connect();
            conn.setAutoCommit(false);
            stmt = conn.createStatement();
            affectedRowCnt = stmt.executeUpdate(query);
 
            conn.commit();
        } catch (SQLException e) {
            conn.rollback();
        } finally {
            if (stmt != null)
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            if (conn != null)
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
 
            ret.put("affectedRow", affectedRowCnt);
        }
 
        return ret;
    }
 
    /*
     * 데이터베이스 조회용 함수 
     * 
     * @author              KYEONGSOO YOO
     * @param   query       조회에 사용할 함수, insert/update문을 이곳에서 사용하지 말것.
     * @return  JSONArray   조회된 내용들 
     * */
    public JSONArray excute (String query) throws ClassNotFoundException, SQLException {
        System.out.println("ex0-start");
        System.out.println(query);
        System.out.println("ex0-end");
        Connection conn = connect();
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery(query);
        
        JSONArray result = new JSONArray();
        
        int colCnt = rs.getMetaData().getColumnCount();
        while (rs.next()) {
            JSONObject item = new JSONObject();
            for (int i = 1; i < colCnt; i++) {
                item.put(rs.getMetaData().getColumnName(i), rs.getString(i));
 
            }
            result.add(item);
        }
 
        if (stmt != null)
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        if (conn != null)
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        if (rs != null)
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
 
        return result;
    }
}
 
cs


함수는 각각 셀렉트를 위한 execute와 업데이트 , 인서트 , 딜리트의 트랜잭션 처리가 동반된 update함수가 있다. 쿼리 자체를 따로 검증하지 않으므로, 실제 사용시에 유의하여야 한다. 




3. DBConnector클래스와 API의 연동


클래스를 만들고 난 이후에 API에 실제 사용하는 모습이다. API의 전문은 깃허브에서 확인 할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
 
    // ================================================================================
    // Sample
    // ================================================================================
 
    /*
     * 예시 함수 
     * 조회를 시행해야 하는 경우.
     * 메소드 리플렉션과 데이터베이스 요청을 포함하기 때문에 예외처리 항목에 
     * ClassNotFoundException / SQLException 항목이 포함되어야 한다. 
     * NOTE : 로그인을 예제로 사용함  
     * */
    public JSONObject mockup_select (JSONObject params) throws ClassNotFoundException, SQLException{
        JSONObject ret = new JSONObject();
           
        
        /*
         * JSONObject params에 아래와 같은 변수들이 들어있는 경우 
         * */
        String mail = (String) params.get("userMail");
        String pw = (String) params.get("userPW");
        
        // 데이터 베이스 요청을 위한 문자열 
        // 잦은 String append는 성능과 가독성에 좋지 않아 String.format을 사용하도록 함 
        String query = String.format("select * from tb_user where userMail = '%s' and userPw = '%' ", mail , pw);
        
        // DB에 연결하기 위함 클래스를 초기화한다.
        // 본 클래스는 util 패키지에 들어있다.
        DBConnector dc = new DBConnector();
        
        // 셀렉트 결과를 가져온다. 
        // 셀렉트시에는 반드시 execute함수를 사용해야 한다.
        // 아래 함수는 insert , update , delete시 사용하지 않도록 조심해야한다.
        // DB 연결 함수들은 항상 try catch문으로 감싸두어야 한다. 오류 검출을 위
        JSONArray db_result = null;
        try {
            db_result = dc.excute(query); 
        }
        catch(SQLException e) {
            // 실제 에러가 발생한 부분을 서버 콘솔상에 출력한다. 
            e.printStackTrace(System.out);
            
            // 실패한 결과에는 반드시 ret JSON에 아래 키들을 포함해야한다. 
            /*
             * METHOD_RESULT_CD         integer             : 결과 코드이다. 실패한 경우 0보다 작은 값을 순서대로 넣는다. 
             * METHOD_ERR_MSG           String              : 요구 사항에 따라 직접 메시지를 삽입.
             * METHOD_ERR_LOCALE_MSG    String              : 예외처리를 통해 오류가 발생한 경우에만 삽입한다. 
             * */
            ret.put("METHOD_RESULT_CD" , -1);
            ret.put("METHOD_ERR_LOCALE_MSG", e.getLocalizedMessage());
        }
        
        // JSONArray 객체는 배열이 아니라 리스트 클래스를 상속받는다.
        // 배열처럼 사용하기 위해서는 먼저 변환해주어야 한다. 
        Object[] items = db_result.toArray();
        
        if(items.length > 0) {
            // 하나 이상 값이 조회되었을 경우 1보다 갯수가 크다. 
            // 로그인이 성공한 것으로 판단하고 데이터를 돌려보낸다.
            
            // 성공한 결과에는 반드시 ret JSON에 아래 키들을 포함해야한다. 
            /*
             * METHOD_RESULT_CD     integer                 : 결과 코드이다. 성공한 경우 1을 출력한다. 
             *                                              만약 실패 한다면 0보다 작은 값으로 코드를 변경한다. (ex : -1)
             * METHOD_RESULT_DATA   JSONObject/JSONArray    : 조회된 결과 데이터이다. JSONObject 혹은 JSONArray를 넣어주도록 한다.
             * */
            ret.put("METHOD_RESULT_CD"1);
            ret.put("METHOD_RESULT_DATA", db_result);
        }
        else {
            // 로그인이 실패한 경우이다. 
            // 이 경우에는 직접 메시지를 넣어주면된다. 서버 오류로 인한 예외가 아니라 사용자 입력에 따른 예외기 떄
            ret.put("METHOD_RESULT_CD" , -2);
            ret.put("METHOD_ERR_MSG""아이디 혹은 비밀번호를 확인해주세요");   
        }
        
        
        return ret;
    }
    
    /*
     * 예시 함수 
     * 업데이트를 시행해야 하는 경우.
     * 메소드 리플렉션과 데이터베이스 요청을 포함하기 때문에 예외처리 항목에 
     * ClassNotFoundException / SQLException 항목이 포함되어야 한다. 
     * NOTE : 사용자 이름 변경하는 예제. 아래의 내용은 insert / update / delete에 공통으로 사용 
     * */
    public JSONObject mockup_update(JSONObject params) throws ClassNotFoundException, SQLException{
        JSONObject ret = new JSONObject();
        // Db와 통신 후 확인할 객체이다. 
        JSONObject dbJSON = null;
       
        // 사용할 값을 가져온다. 
        String userName = (String) params.get("userName");
        int userSeq = (int) params.get("userSeq");
        
        // 항목이 null 혹은 공백문자가 아닌지 확인 
        // 일부러 비워두도록 업데이트하는것이 아니라면 항상 확인해야한다. 
        // insert / update / delete 항목들은 되도록 엄격하게 유효성 검사를 거쳐야 한다. 
        if(userName == null || userName.isEmpty()) {
            ret.put("METHOD_RESULT_CD"-1);
            ret.put("METHOD_ERR_MSG""이름을 입력해주세요");
        }
        else {
            // 업데이트에 필요한 쿼리문을 작성 
            String query = String.format("update TB_User set userName = '%s' where userSeq = '%s'", userName , userSeq);
            
            // 데이터 베이스 연결용 클래스를 초기화해준다. 
            DBConnector dc = new DBConnector();
            
            try {
                // 위와 동일하다 
                // insert / update / delete를 사용할때는 아래 함수를 사용하도록 한다. 
                // 아래 함수는 오류가 발생하면 다시 디비를 이전 상태로 복구한다. 
                dbJSON = dc.update(query);
            }
            catch(SQLException e) {
                e.printStackTrace(System.out);
                ret.put("METHOD_RESULT_CD"-1);
                ret.put("METHOD_ERR_MSG""통신 중 오류가 발생했습니다");
                ret.put("METHOD_ERR_LOCALE_MSG", e.getLocalizedMessage());
            }
            
            // 실제 결과를 담아둔다. 
            // 이 데이터에는 실제로 영향을 받은 데이터들의 row count가 들어간다. 
            // 한건의 업데이트만 발생하면 1만 넘어간다. 아무것도 영향받지 못하면 0이 넘어간다. 
            ret.put("METHOD_RESULT_DATA", dbJSON.get("affectedRow"));
        }
        
        return ret;
    }
 
cs


'DEV > JSP' 카테고리의 다른 글

[JSP 1.x] ReflectionMethod를 이용한 API 서블릿 구현  (0) 2018.06.20
학교에서 배운 JSP 내용을 정리해둡니다.

본 내용은 일반 JSP 환경에서 Reflection Method를 이용해 API 서블릿을 만드는 방법을 남겨둔 것입니다. 
파라미터와 반환값은 JSONObject를 사용하므로, 가벼운 JSON 관련 클래스를 사용했습니다.



본 기능의 목적은 아래와 같다 .


1. 특정 서블릿의 주소로 비동기 통신 요청 

2. 요청은 서블릿으로 받는다. ex) /api/user

3. 실행할 함수는 JSON 객체로 받는다 ex) { 'fn_nm' : 'get' , 'params' : {  ... } }


위 단계를 구현하기 위해서 먼저 스트링으로 받은 함수명을 reflection method로 실행하는 부분이 필요한데, 그부분은 아래와같이 클래스로 분리하였다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package util;
import java.lang.reflect.*;
import org.json.simple.*;
/*
 * API 서블릿에서 리플렉션을 사용하기 위한 클래스 
 * 
 * @author          KYEONGSOO YOO
 * @Date            2018-06-01
 * */
public class ReflectionMethod {
    // ================================================================================
    // Properties
    // ================================================================================
    private static Class<>; _parentClass;
    private static Method[] _parentMethods;
    private static Object _parentClassInstance;
    // ================================================================================
    // Constructor
    // ================================================================================
    /*
     * ReflectionMEthod 클래스의 생성자 함수이다. 인스턴스가 존재해야한다. 보통 실행할 함수가 속한 클래스의 this를 넘겨주면
     * 된다.
     * 
     * 수정 2018-06-19 KYEONGSOO YOO
     * 초기화 부분을 함수 실행시로 옮김 
     * 
     * @author                  KYEONGSOO YOO
     * 
     */
    public ReflectionMethod() {
        super();
    }
    
    // ================================================================================
    // private methods
    // ================================================================================
    /*
     * 내부 함수이다. 생성자가 제대로 동작했는지 체크한다.
     * 
     * @author                  KYEONGSOO YOO
     * @return                  생성자가 제대로 생성되었는지 여부 (Boolean)
     */
    private boolean chkState() {
        return !(_parentMethods == null || _parentMethods.length &lt; 0);
    }
    /*
     * 현재 클래스에서 String을 기준으로 함수를 가져온다. 만약 함수가 존재하지 않으면 null을 리턴한다.
     * 
     * @author                  KYEONGSOO YOO
     * @param   name            가져올 함수의 이름
     * @return                  가져온 함수 , 존재하지 않으면 null
     */
    private Method get(String name) {
        for (Method fn : _parentMethods) {
            if (fn.getName().equals(name)) {
                return fn;
            }
        }
        System.out.println("[RM] can not find method by [" + name + "]");
        return null;
    }
    
    private boolean setInstance(Object instance) {
        if(instance != null) {
            _parentClassInstance = instance;
            _parentClass = _parentClassInstance.getClass();
            _parentMethods = _parentClass.getDeclaredMethods();
            return true;
        }
        else {
            return false;
        }
        
    }
    
    /*
     * 현재 클래스에 함수가 존재하는지 여부를 확인한다.
     * 
     * @author                  KYEONGSOO YOO
     * @param   name            가져올 함수의 이름
     * @return                  함수의 존재 여부
     */
    private boolean exists(String fn_nm) {
        for (int i = 0; i &lt; _parentMethods.length; i++) {
            // System.out.println(_parentMethods[i]);
            if (_parentMethods[i].getName().equals(fn_nm)) {
                return true;
            }
        }
        return false;
    }
    /*
     * 현재 클래스에서 함수를 가져오고 실제로 실행한다. 변수들은 json으로 받고 그 결과도 json으로 넘겨준다.
     * 
     * @author              KYEONGSOO YOO
     * @param   fn_nm       가져올 함수의 이름
     * @param   params      함수에 넘겨줄 매개변수들, json
     * @return              함수 실행 결과, json
     */
    private JSONObject invoke(String fn_nm, JSONObject params) {
        JSONObject obj = new JSONObject();
        try {
            Method fn = get(fn_nm);
            Object value = fn.invoke(_parentClassInstance, params);
            obj.put("INVOKE_RESULT_CD"1);
            obj.put("INVOKE_DATA", value);
            return obj;
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            obj.put("INVOKE_RESULT_CD"-1);
            obj.put("INVOKE_ERR_MSG", e.getLocalizedMessage());
            return obj;
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            obj.put("INVOKE_RESULT_CD"-2);
            obj.put("INVOKE_ERR_MSG", e.getLocalizedMessage());
            return obj;
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            obj.put("INVOKE_RESULT_CD"-3);
            obj.put("INVOKE_ERR_MSG", e.getLocalizedMessage());
            return obj;
        }
    }
    /*
     * 클래스의 함수를 실행하고 결과 json까지 만들어준다. invoke, exists, get을 합친 것이다. 본 함수를 사용할
     * 
     * @author              KYEONGSOO YOO
     * @param   fn_nm       가져올 함수의 이름
     * @param   params      함수에 넘겨줄 매개변수들, json
     * @return              함수 실행 결과, json
     */
    private JSONObject callAndGetResult(String fn_nm, JSONObject params) {
        JSONObject jsonObj = new JSONObject();
        if (chkState()) {
            if (exists(fn_nm)) {
                JSONObject ret = invoke(fn_nm, params);
                int resultCD = (int) ret.get("INVOKE_RESULT_CD");
                if (resultCD &gt; 0) {
                    jsonObj.put("RESULT""SUCCESS");
                    jsonObj.put("RESULT_CD"1);
                    jsonObj.put("RESULT_DATA", ret.get("INVOKE_DATA"));
                } else {
                    jsonObj.put("RESULT""FAIL");
                    jsonObj.put("RESULT_CD"-2);
                    jsonObj.put("ERR_MSG""[RM] 요청된 함수 [" + fn_nm + "]의 실행 중 오류가 발생했습니다.");
                    jsonObj.put("INVOKE_ERR_MSG", ret.get("INVOKE_ERR_MSG"));
                    jsonObj.put("INVOKE_ERR_CD", ret.get("INVOKE_RESULT_CD"));
                }
            } else {
                jsonObj.put("RESULT""FAIL");
                jsonObj.put("RESULT_CD"-1);
                jsonObj.put("ERR_MSG""[RM] 요청된 함수 [" + fn_nm + "]이 존재하지 않습니다.");
                System.out.println("METHOD IS NOT EXISTS - start");
                for(int i = 0 ; i &lt; _parentMethods.length ; i++) {
                    System.out.println(_parentMethods[i].getName());
                }
                System.out.println("METHOD IS NOT EXISTS - end");
            }
        } else {
            jsonObj.put("RESULT""FAIL");
            jsonObj.put("RESULT_CD"0);
            jsonObj.put("ERR_MSG""[RM] 리플렉션 클래스가 정상적으로 초기화되지 않았습니다.");
        }
        return jsonObj;
    }
    
    // ================================================================================
    // public Methods
    // ================================================================================
    /*
     * 현재 클래스에 함수가 존재하는지 여부를 확인한다.
     * 
     * @author                  KYEONGSOO YOO
     * @param   name            가져올 함수의 이름
     * @param   instance        확인할 클래스의 인스턴스 
     * @return                  함수의 존재 여부
     */
    public boolean exists(String fn_nm , Object instance) {
        if(setInstance(instance)) {
            return exists(fn_nm);
        }
        else {
            return false;
        }
    }
    /*
     * 클래스의 함수를 실행하고 결과 json까지 만들어준다. invoke, exists, get을 합친 것이다. 본 함수를 사용할
     * 
     * @author              KYEONGSOO YOO
     * @param   fn_nm       가져올 함수의 이름
     * @param   params      함수에 넘겨줄 매개변수들, json
     * @param   instance        확인할 클래스의 인스턴스 
     * @return              함수 실행 결과, json
     */
    public JSONObject callAndGetResult(String fn_nm , JSONObject params, Object instance) {
        if(setInstance(instance)) {
            return callAndGetResult(fn_nm, params);
        }else {
            JSONObject ret = new JSONObject();
            ret.put("RESULT" , "FAIL");
            ret.put("RESULT_CD"1);
            ret.put("ERR_MSG" , "클래스의 인스턴스가 주어지지 않았습니다.");
            return ret;
        }
    }
}
 
cs



아래와 같이 분리한 이후에는 특정 주소를 처리 하는 서블릿을 만들고 아래와 같이 구현한다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package api;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.ws.http.HTTPException;
 
import java.sql.*;
import util.*;
import org.json.simple.*;
import org.json.simple.parser.*;
 
/**
 * Servlet implementation class users
 */
@WebServlet("/API/users")
public class users extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static ReflectionMethod rm;
    private CommonVariable cv;
 
    private HttpSession _session = null;
 
    /**
     * @see HttpServlet#HttpServlet()
     */
    public users() {
        super();
        // TODO Auto-generated constructor stub
        rm = new ReflectionMethod();
        cv = CommonVariable.getInstance();
    }
 
    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     *      response)
     */
 
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        _session = request.getSession();
 
        JSONParser parser = new JSONParser();
        JSONObject prms = null;
        try {
            prms = (JSONObject) parser.parse(request.getParameter("params"));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        JSONObject jsonObj = rm.callAndGetResult(request.getParameter("fn"), prms ,this);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().append(jsonObj.toJSONString());
 
    }
 
    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     *      response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // TODO Auto-generated method stub
        // doGet(request, response);
        JSONParser parser = new JSONParser();
        JSONObject prms = null;
        try {
            prms = (JSONObject) parser.parse(request.getParameter("params"));
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        _session = request.getSession();
 
        JSONObject jsonObj = rm.callAndGetResult(request.getParameter("fn"), prms, this);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().append(jsonObj.toJSONString());
 
    }
}
 
cs


위와 같이 구현한 경우 요청 시에는 {  fn_nm : 'mockup' , params: { ... } } 형태로 값을 넘기면, fn_nm에 문자열로 넘긴 함수명을 찾아 실행하게 된다. 

아래에 만약 mockup 이라는 함수를 구현해 사용한다면,

public JSONObject mockup(JSONObject params);

라는 형태로 함수를 구현하면 된다.  


아래는 mysql과 연동 하였을 경우의 예제이다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
     * 예시 함수 
     * 조회를 시행해야 하는 경우.
     * 메소드 리플렉션과 데이터베이스 요청을 포함하기 때문에 예외처리 항목에 
     * ClassNotFoundException / SQLException 항목이 포함되어야 한다. 
     * NOTE : 로그인을 예제로 사용함  
     * */
    public JSONObject mockup_select (JSONObject params) throws ClassNotFoundException, SQLException{
        JSONObject ret = new JSONObject();
           
        
        /*
         * JSONObject params에 아래와 같은 변수들이 들어있는 경우 
         * */
        String mail = (String) params.get("userMail");
        String pw = (String) params.get("userPW");
        
        // 데이터 베이스 요청을 위한 문자열 
        // 잦은 String append는 성능과 가독성에 좋지 않아 String.format을 사용하도록 함 
        String query = String.format("select * from tb_user where userMail = '%s' and userPw = '%' ", mail , pw);
        
        // DB에 연결하기 위함 클래스를 초기화한다.
        // 본 클래스는 util 패키지에 들어있다.
        DBConnector dc = new DBConnector();
        
        // 셀렉트 결과를 가져온다. 
        // 셀렉트시에는 반드시 execute함수를 사용해야 한다.
        // 아래 함수는 insert , update , delete시 사용하지 않도록 조심해야한다.
        // DB 연결 함수들은 항상 try catch문으로 감싸두어야 한다. 오류 검출을 위
        JSONArray db_result = null;
        try {
            db_result = dc.excute(query); 
        }
        catch(SQLException e) {
            // 실제 에러가 발생한 부분을 서버 콘솔상에 출력한다. 
            e.printStackTrace(System.out);
            
            // 실패한 결과에는 반드시 ret JSON에 아래 키들을 포함해야한다. 
            /*
             * METHOD_RESULT_CD         integer             : 결과 코드이다. 실패한 경우 0보다 작은 값을 순서대로 넣는다. 
             * METHOD_ERR_MSG           String              : 요구 사항에 따라 직접 메시지를 삽입.
             * METHOD_ERR_LOCALE_MSG    String              : 예외처리를 통해 오류가 발생한 경우에만 삽입한다. 
             * */
            ret.put("METHOD_RESULT_CD" , -1);
            ret.put("METHOD_ERR_LOCALE_MSG", e.getLocalizedMessage());
        }
        
        // JSONArray 객체는 배열이 아니라 리스트 클래스를 상속받는다.
        // 배열처럼 사용하기 위해서는 먼저 변환해주어야 한다. 
        Object[] items = db_result.toArray();
        
        if(items.length > 0) {
            // 하나 이상 값이 조회되었을 경우 1보다 갯수가 크다. 
            // 로그인이 성공한 것으로 판단하고 데이터를 돌려보낸다.
            
            // 성공한 결과에는 반드시 ret JSON에 아래 키들을 포함해야한다. 
            /*
             * METHOD_RESULT_CD     integer                 : 결과 코드이다. 성공한 경우 1을 출력한다. 
             *                                              만약 실패 한다면 0보다 작은 값으로 코드를 변경한다. (ex : -1)
             * METHOD_RESULT_DATA   JSONObject/JSONArray    : 조회된 결과 데이터이다. JSONObject 혹은 JSONArray를 넣어주도록 한다.
             * */
            ret.put("METHOD_RESULT_CD"1);
            ret.put("METHOD_RESULT_DATA", db_result);
        }
        else {
            // 로그인이 실패한 경우이다. 
            // 이 경우에는 직접 메시지를 넣어주면된다. 서버 오류로 인한 예외가 아니라 사용자 입력에 따른 예외기 떄
            ret.put("METHOD_RESULT_CD" , -2);
            ret.put("METHOD_ERR_MSG""아이디 혹은 비밀번호를 확인해주세요");   
        }
        
        
        return ret;
    }
    
cs


'DEV > JSP' 카테고리의 다른 글

[JSP 1.x] DB 사용부를 클래스로 분리하기  (0) 2018.06.20

ㄱ본 글은 자바스크립트 카테고리의 "하이브리드 앱에서의 함수 실행" 글과 이어집니다. 



Objective C 네이티브로 하이브리드 앱을 만들때에 웹에서 Objective C 함수를 실행하기 위해서 자바스크립트단에도 준비를 해야하지만, 실제로 실행될 Obj C 함수도 어플리케이션단에 준비되어있어야한다. 이때, 기존의 UIWebView와 iOS8부터 사용되고 있는 WKWebView에서의 처리가 각각 다르기에 각 클래스에 맞게 정리해둔다.


1. 자바스크립트 -> iOS


1.1. UIWebView

 

 기존에 많이 사용되던 클래스이다. 자바스크립트 사이드에서의 구현은 간단하지만 어플리케이션 레벨에서 약간 까다로운 준비를 해야한다. 

아래의 코드는 하나의 뷰 컨트롤러에서 webView:shouldStartLoadWithRequest:navigationType: 함수를 사용하여 웹뷰의 request를 가로채 

필요한 값들만 뽑아낸다. 요청의 프로토콜이 기존에 약속되어 있던 코드인지 확인 한 후 host는 함수명으로 querystring은 키 / 변수 쌍으로 사용하고, querystring을 사용할때 Dictionary로 변환하여 선언해둔 함수로 넘겨준다. 넘겨받은 함수명으로 바로 함수를 사용할 경우 오류가 발생할 수 있으므로 RespondToSelector: 함수로 넘겨받은 함수가 실제로 존재하는지 확인한 후 실행하도록 했다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#import <UIKit/UIKit.h>
 
// UIWebView의 Delegate함수를 실행해야 하므로 Delegate를 추가해준다.
@interface UIWVController: UIViewController<UIWebViewDelegate>
 
// 사용할 웹뷰를 선언해준다.
@property(nonatomic) IBOutlet UIWebView* webView;
 
@end
 
@implementation FrontViewController
 
 
- (void)viewDidLoad {
    [super viewDidLoad];
    // 웹뷰를 초기화 해준다.    
    webView = [[UIWebView allocinit];
    // 해당 웹뷰의 Delegate를 현재 ViewController로 지정해준다.
    [webView setDelegate:self];
}
 
#pragma mark - UIWebView Delegate Methods
 
// 웹뷰의 리퀘스트가 시작되면 (주소가 바뀌면) 실행되는 함수
// UIWebView일때 location.href 혹은 <a href=''.../> 를 사용하면 아래 함수가 실행된다.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
navigationType:(UIWebViewNavigationType)navigationType{
    if([request.URL.scheme rangeOfString:@"ioscall"].location != NSNotFound){
        // web -> app 통신을 걸러낸다
        // 이 예제에서는 ioscall이라는 프로토콜을 사용하는 것을 전제로 함
        
        // scheme : 요청의 프로토콜명 보통은 http / https가 넘어온다.
        // host : 함수의 이름 
        // query : 파라미터
        
        // 넘겨받은 함수를 확인 및 실행하기 위한 NSSelector 변수
        SEL _selector_from_web;
        // 변수가 있는지 없는지 확인한다.
        // 변수가 존재하는 경우에는 Selector에 : 가 붙으므로 나누어 선언해준다.
        if(request.URL.query){
            NSLog(@"변수가 있습니다. %@" , request.URL.query);
            _selector_from_web = NSSelectorFromString([NSString stringWithFormat:@"%@:" , request.URL.host]);
        }
        else{
            NSLog(@"변수가 없습니다. %@" , request.URL.query);
            _selector_from_web = NSSelectorFromString(request.URL.host);
        }
        
        if([self respondsToSelector:_selector_from_web]){
            if(request.URL.query){
                // 변수가 존재하는 경우
                Boolean err_occurred = false;     
                // 넘겨받은 쿼리스트링을 &를 기준으로 나눕니다           
                NSArray *queries_arr = [[request.URL.query stringByRemovingPercentEncoding] 
componentsSeparatedByString:@"&"];
                NSMutableDictionary *queries = [[NSMutableDictionary alloc]init];
                for(int idx = 0 ; idx < queries_arr.count ; idx++){
                    if([[queries_arr objectAtIndex:idx] rangeOfString:@"="].location == NSNotFound){
                        // 형식이 잘못되었을 경우
                        err_occurred = true;
                        break;
                    }
                    NSArray *separated = [[queries_arr objectAtIndex:idx] componentsSeparatedByString:@"="];
                    [queries setObject:[separated objectAtIndex:1] forKey:[separated objectAtIndex:0]];
                }
                if(err_occurred){
                    // 에러가 생겼다.
                    NSLog(@"올바르지 않은 형태의 쿼리입니다. %@" , request.URL.query);
                    return NO;
                }
                else{
                    // 2. method call
                    // 함수가 컨트롤러에 존재할때만 실행되도록 되어있습니다.
                    [self performSelector:_selector_from_web withObject:queries];
                }            }
            else {
                // 변수가 존재하지 않는 경우
                [self performSelector:_selector_from_web];
            }
        }
        else{
            // 넘겨받은 함수가 존재하지 않는 경우 
            NSLog(@"셀렉터가 존재하지 않습니다");
            return NO;
        }
    }    
 
    return YES;    
}
 
 
#pragma mark - JS -> APP Methods
 
- (void) mockup_method:(NSDictionary *)params{
    // 스크립트에서 location.href = ioscall://mockup_method?value=1을 실행할 경우
    // 이 함수가 실행됩니다.
    NSLog(@"mockup method called ! value = [%@]" , [params objectForKey:@"value"]);
}
 
@end

cs



1.2. WKWebView


 WKWebView는 UIKit이외에 WebKit을 추가해주어야한다. 좀 더 추가해주어야 하는 요소들이 많고 상속받아야 하는 것들이 많지만 그만큼 더 세밀한 

작업을 지원한다. 스토리보드에서는 WKWebView를 추가해 사용할 수 없으므로 크기를 지정해주고 직접 뷰에 추가해주어야 하는 불편함이 있다. request를 다루는 부분과 스크립트를 다루는 부분이 분리되어 있어 코드를 보기에 더 편해졌다. UIWebView와 마찬가지로 함수가 존재할때만 실행하도록 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#import <UIKit/UIKit.h>
// WkWebView를 사용하기 위해 WebKit을 참조합니다.
@import WebKit;
 
// WkWebView에서 주소가 변경되거나 스크립트를 사용할 경우 사용할 Delegate함수를 사용하기 위해
// 아래의 Delegate들을 참조합니다
@interface MainViewController : UIViewController<WKUIDelegate , WKNavigationDelegate , WKScriptMessageHandler>
 
@property(nonatomic) WKWebView* webView;
 
@end
 
@implementation MainViewController{
    // 지역 변수를 선언합니다. 
    // 웹뷰의 랜더 속도 및 각종 설정들을 담당하는 클래스입니다.
    WkWebViewConfiguration *config;
    // 자바스크립트에서 메시지를 받거나 자바스크립트를 실행하는데 필요한 클래스입니다.
    WKUserContentController *jsctrl;
}
 
- (void) viewDidLoad{
    [super viewDidLoad];
 
    // WkWebViewConfiguration과 WKUserContentController를 초기화해줍니다. 
    config = [[WKWebViewConfiguration alloc]init];
    jsctrl = [[WKUserContentController alloc]init];
    
    // 자바스크립트 -> ios에 사용될 핸들러 이름을 추가해줍니다.
    // 본 글에서는 핸들러 및 프로토콜을 ioscall로 통일합니다.
    [jsctrl addScriptMessageHandler:self name:@"ioscall"];
    // WkWebView의 configuration에 스크립트에 대한 설정을 정해줍니다.
    [config setUserContentController:jsctrl];
    
    // 웹뷰의 딜리게이트들을 새로 초기화해줍니다.
    [webView setUIDelegate:self];
    [webView setNavigationDelegate:self];
    
    CGRect frame = [[UIScreen mainScreen]bounds];
    // WkWebView는 IBOutlet으로 제공되지 않아 스토리보드에서 추가할 수 없습니다.
    // 웹뷰의 크기를 정해준 후 초기화하고 본 ViewController의 뷰에 추가합니다.
    webView = [[UIWKWebView alloc] initWithFrame:frame configuration:config];
    [[self view]addSubView:webView];
}
 
#pragma mark - delegate method
 
// WKScriptMessageHandler에 의해 생성된 delegate 함수입니다. 
// 자바스크립트에서 ios에 wekkit핸들러를 통해 postMessage함수를 사용한 경우 실행됩니다.
- (void)userContentController:(WKUserContentController *)userContentController 
        didReceiveScriptMessage:(WKScriptMessage *)message{
 
    // message의 body에 전달받은 객체가 NSDictionary형식으로 들어있습니다.
    // 본 예제에서는 함수이름을 "name"으로, 파라미터 묶음을 "params"로 정합니다.
    if([[[message body] allKeys] count] > 0 && [[message body]objectForKey:@"name"]){
        SEL _selector = nil;
        if([[message body] objectForKey:@"params"]){
            // 변수를 가진 경우
            _selector = NSSelectorFromString([NSString stringWithFormat:@"%@:"
, [[message body] objectForKey:@"name"]]);
            
            if([self respondsToSelector:_selector]){
                // 현재 뷰 컨트롤러에 함수가 존재하는지 확인합니다.
                [self performSelector:_selector withObject:[[message body]objectForKey:@"params"]];
            }
            else{
                NSLog(@"셀렉터가 존재하지 않습니다");
            }
        }
        else{
            _selector = NSSelectorFromString([NSString stringWithFormat:@"%@",
[[message body]objectForKey:@"name"]]);
            
            if([self respondsToSelector:_selector]){
                // 현재 뷰 컨트롤러에 함수가 존재하는지 확인합니다. 
               [self performSelector:_selector];
            }
            else{
                NSLog(@"셀렉터가 존재하지 않습니다.");
            }
        }
    }
    else{
        NSLog(@"잘못된 javascript 요청입니다.");
    }
}
 
#pragma mark - JS -> ios Method
 
- (void) mockup_method:(NSDictionary *)params{
    // 스크립트에서 webkit.messageHandlers.ioscall.postMessage 함수를 실행할 경우
    // 이 함수가 실행됩니다.
    NSLog(@"mockup method called ! value = [%@]" , [params objectForKey:@"value"]);
}
@end
 
 
cs



2. iOS -> 자바스크립트


 iOS상에서 자바스크립트를 실행하는 것도 UIWebView와 WKWebView간 차이가 있다. 반대의 경우보다는 비교적 간단하다.


2.1. UIWebView

 

 UIWebView에서는 뷰 컨트롤러 내에서 stringByEvaluatingJavascriptFromString함수를 이용해 자바스크립트를 호출한다. 다만 스크립트를 직접 

문자열로 만들어 호출하는만큼 문자열을 조작하는 함수를 따로 만들어 두는 것이 편할 것이다. 


1
[webView stringByEvaluatingJavascriptFromString:@"window.alert('Hello World')"];
cs

2.2. WKWebView


 WKWebView에서의 스크립트 호출도 UIWebView와 비슷한 방법으로 실행된다. 함수는 evaluateJavasScript:completionHandler: 이며 

completionHandler에 블록함수를 넣어 자바스크립트가 실행된 뒤의 이벤트를 캐치할 수 있다. 



1
2
3
[webView evaluateJavaScript:@"window.alert('Hello World');" completionHandler:^{
    NSLog(@"evaluate Completed");
}];
cs


즉각적으로 자바스크립트를 실행할 경우 UIWebView와 사용법이 크게 다르지 않지만, WKWebView에서는 차별화된 기능을 갖고 있는데, WKUserScript

클래스를 이용해 문서 최상단 / 최하단에 스크립트를 직접 삽입해주는 addScript: 함수가 그것이다. 사용법은 아래와 같다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (void) viewDidLoad{
    [super viewDidLoad];
 
    // WkWebViewConfiguration과 WKUserContentController를 초기화해줍니다. 
    config = [[WKWebViewConfiguration alloc]init];
    jsctrl = [[WKUserContentController alloc]init];
    
    // 자바스크립트 -> ios에 사용될 핸들러 이름을 추가해줍니다.
    // 본 글에서는 핸들러 및 프로토콜을 ioscall로 통일합니다.
    [jsctrl addScriptMessageHandler:self name:@"ioscall"];
    // WkWebView의 configuration에 스크립트에 대한 설정을 정해줍니다.
    [config setUserContentController:jsctrl];
    
    // 스크립트를 초기화 합니다.
    WKUserScript *cookie_script = [[WKUserScript alloc]initWithSource:@"alert('load!')" 
                                    injectionTime:WKUserScriptInjectionTimeAtDocumentStart 
                                    forMainFrameOnly:NO];
    // UserContentController에 스크립트를 삽입합니다.
    [jsctrl addUserScript:cookie_script];
        
    // 웹뷰의 딜리게이트들을 새로 초기화해줍니다.
    [webView setUIDelegate:self];
    [webView setNavigationDelegate:self];
    
    CGRect frame = [[UIScreen mainScreen]bounds];
    // WkWebView는 IBOutlet으로 제공되지 않아 스토리보드에서 추가할 수 없습니다.
    // 웹뷰의 크기를 정해준 후 초기화하고 본 ViewController의 뷰에 추가합니다.
    webView = [[UIWKWebView alloc] initWithFrame:frame configuration:config];
    [[self view]addSubView:webView];
}
cs


소스는 위의 WKWebView 사용부의 viewDidLoad 부분을 잘라낸 것인데 , ln14 ~ 18이 추가되었다. WKUserScript클래스로 문서의 최상단에 둘지

최하단에 둘지 결정하고 (injectionTime) 모든 프레임에 적용할 것인지 (forMainFrameOnly)결정한 후 WKUserContentController에 해당 스크립트를

추가해준다. 이렇게하면 웹뷰가 로드되었을때 미리 삽입한 스크립트들이 자동으로 실행되게 된다. 



안드로이드 , iOS에서 네이티브로 하이브리드 앱을 만들 경우 웹(JS)와  앱(ios , android)양쪽에서 서로의 함수를 실행해야 하는 경우가 생긴다. 


본 글에는 웹 <-> 앱간 함수를 실행해야 될 경우 자바스크립트에서 준비해야할 함수를 정리해둔다.


(앱에서 자바스크립트를 실행하는 코드와 자바스크립트로 앱 함수를 실행할 경우 앱에서 선언해야 하는 앱(ios / android)의 함수는 따로 다루도록 한다.)




1. 자바스크립트에서 iOS 함수 실행


 자바스크립트에서 iOS의 함수를 실행하는 방법은 두가지이다. 이는 기존에 사용되던 웹뷰 UIWebView에 더해 iOS8부터 WKWebView가 추가되었기 때문이다. 두 함수의 자바스크립트 코드와 objective c (OR Swift)코드가 다르기 때문에 유의하여야 한다. 


1.1 UIWebView

location.href = YOUR_PROTOCOL://YOUR_METHOD_NAME?PARAM_1_NAME=PARAM_2_VALUE&PARAM_2_NAME=PARAM_2_VALUE....

 UIWebView에서는 주소를 호출하는 방식을 사용한다. http 대신 임의로 지정한 이름을 프로토콜로 사용하고 도메인이 위치하는 곳에는 함수 이름을 , 쿼리스트링으로 함수의 변수들을 넘겨준다. 이렇게 실행 후 iOS 코드 내에서는 웹뷰의 주소가 변경되는 것을 감지하는 함수내에서 각 값들을 분리하여 사용하도록 한다. 


1.2 WKWebview 

 

window.webkit.messageHandlers[YOUR_HANDLER_NAME].postMessage(PARAMS)


 WKWebView에서는 전역변수로서 자동으로 webkit이라는 변수를 생성한다. 그리고 웹 -> 앱간 통신이 필요한 경우 위의 webkit 변수를 통해 이루어진다. webkit.messageHandlers에 앱에서 임의로 결정한 값이 추가로 생성되며, 그 자식 함수로 postMessage라는 함수가 생성된다. 여기서 postMessage라는 함수를 스크립트에서 실행할 경우 자바스크립트 함수를 처리하는 WKWebView의 딜리게이트 함수에서 'PARAMS'을 dictionary형태로 넘겨 받게 된다. PARAMS는 자바스크립트 객체 형태로 넘겨주어도 무방하다. 


2. 자바스크립트에서 Android 함수 실행


window[YOUR_HANDLER_NAME][YOUR_METHOD_NAME](PARAMS)

 안드로이드에서는 임의로 설정한 HANDLER이름으로 전역 변수를 생성하고 그 아래에 함수를 선언하는 형식이 된다. 일반적으로 전역 변수로서 네임스페이스로 함수들을 묶어두는 방식과 동일하다. 





위의 기능들을 사용하는데 매번 문자열을 더하거나 확인해가면서 작업할 수는 없는 노릇이라, 웹 <-> 앱간의 통신을 담당하는 스크립트 함수들을 묶음으로 만들어두고 실제로 사용하였다. 가지고 있는 기능은 아래와 같다. 

// 0. 최초 로드시

// 반드시 HY_BRIDGE.config 함수로 ios 및 안드로이드의 핸들러 / 프리픽스를 초기화 해주어야 합니다.

HY_BRIDGE.config({

ios : { prefix : 'PREFIX' , handler : 'HANDLER' },

android : { handler : 'HANDLER' }

})


// 1.1 기기 타입 가져오기

HY_BRIDGE.tools.device() // iOS / Android / Unknown

// 1.2 iOS 확인

HY_BRDIGE.tools.isiOS() // true or false

// 1.3 Android 확인

HY_BRIDGE.tools.isAndroid() // true or false



// 2 웹-> 어플리케이션 함수

// 2.1 생성

HY_BDIRGE.app.extend({

method_1 : function(foo,bar){

HY_BDRIDGE.app.dispatch({

method : "METHOD_NAME_IN_APP",

params : { foo : 0 , bar : 1 }

});

},

method_2 : function(){

..

}

})

// 2.2 실행

HY_BRIDGE.app.call(method_1 , foo , bar);


// 2.3 특정 이벤트일때

// 2.3.1 생성

HY_BRIDGE.app.evt.insert(HY_BRIDGE_app.IDENTIFIER.ON_LOAD_FINISHED, function(){

// 함수 내용

})

// 2.3.2 (앱에서) 실행 (선언된 순서대로 실행됩니다.)

HY_BRIDGE.app.evt.flush(HY_BRIDGE.app.IDENTIFIER.ON_LOAD_FINISHED);


// 3 어플리케이션 -> 웹

// 어플리케이션에서 자바스크립트는 쉽게 실행할 수 있지만, 파편화 되는 것을 방지하기 위해 선언 및 사용부를 통일해두었다.

// 3.1.1 함수 생성

HY_BRIDGE.web.extend({

method_1 : function(foo){

console.log("Hello " + foo)

}

});

// 3.1.2 함수 실행

HY_BRIDGE.web.call('method_1' , foo)

// 3.2.1 변수 생성

HY_BRIDGE.web.declare({ value_1 : 0 , value_2 : 1 })

// 3.2.2 변수 가져오기

HY_BRIDGE.web.get('value_1')




'DEV > JavaScript' 카테고리의 다른 글

문자열 관련 함수 묶음  (0) 2017.10.17
쿠키 관리 함수 묶음  (0) 2017.10.17
주소의 쿼리스트링을 조작하는 함수 모음  (0) 2017.10.17

문자열에서 특정 문자를 가지고 있는지 확인하는 경우 사용하는 함수를 묶어두었다


문자열에서 영문자 (대소구분x) , 영 대문자 , 영 소문자 , 숫자 , 특수문자 , 한글 , 공백 , 줄바꿈 등을 묶어 두었다.

그리고 trim이 존재하지 않는 경우 직접 만들어준다. 



클라이언트에서 쿠키를 조작하는 함수를 따로 모아두었다

크롬에서 쿠키 조작의 조건이 엄격해져서 해당 주소의 쿠키만 가져올 수 있는것 같다.


쿠키 생성 , 가져오기 , 삭제 , 값 넣기 , 모든 쿠키 가져오기 , 쿠키 비우기 등을 할 수 있다.


클라이언트 상에서 쿼리스트링을 뽑아 사용하는 경우가 종종 있어 따로 함수 화 해두었다.


모든 쿼리스트링 가져오기 , 존재 여부 , 특정 쿼리스트링 가져오기 , 객체 -> 쿼리스트링 변환 등 기능을 넣어뒀다. 



'DEV > JavaScript' 카테고리의 다른 글

하이브리드 앱에서의 함수 실행  (1) 2017.10.17
문자열 관련 함수 묶음  (0) 2017.10.17
쿠키 관리 함수 묶음  (0) 2017.10.17

Heroku X NodeJS

2. Heroku와 Github 연동하기



1. 새로운 Heroku 어플리케이션 생성 


 heroku 관련글을 정말 오랜만에 올리는 것 같다. 최근에 heroku를 이용한 nodejs application을 간단하게 개발해보는 스터디를 진행하면서 관련된 정보들을 여기에 남겨두려고한다. 시작은 강좌글 같은 느낌이었는데 아마도 일지 느낌으로 변하게 될 것 같다. 

 

 글에서야 한페이지 차이지만 제법 시간이 지난관계로 새로 어플리케이션을 생성하고 프로젝트를 진행하려고한다. 어플리케이션 생성이 조금 달라진 것들이 있는데 그 중 하나는, heroku CLI에서 명령어로 어플리케이션을 만드는 경우 git만 생성될뿐 git clone하여도 git을 제외하곤 아무파일도 들어있지 않는 상태로 생성된다는 점. 아래 화면을 보면 확인해볼 수 있다. 


우선 어플리케이션 생성은 이전과 같다. 


heroku login // heroku CLI에 먼저 로그인하자

heroku create YOUR-APP-NAME // heroku 어플리케이션을 생성한다.

git clone https://git.heroku.com/YOUR-APP-NAME // 깃을 이용해 heroku 파일을 복사해온다.



dir 명령어를 실행해도 아무것도 안들어 있음을 확인할 수있다. 이제 여기에 express를 이용해 기본 골자를 만들어 다시 heroku에 배포(deploy)할 예정이다. 여기서는 간단한 express 사용법만 다루고 상세한 express의 기능은 기회가 되면 마저 이어가는걸로


2. express-generator로 express 스캐폴딩  


어플리케이션의 기본 구조를 빠르게 만들어보고 github를 연동해보는 단계까지 진행해볼 계획이기때문에 express-generator로 간단한 어플리케이션 구조를 만들어 보려고한다. 일단 nodejs를 설치했으면 npm이 설치 되어있을테니 npm으로 본작업에 필요한 것들을 준비해보자.


npm install -g express // express를 먼저 설치해주자. -g는 해당 폴더가 아닌 전역 설치를 이야기한다.

npm install -g express-generator // express를 사용하는 node어플리케이션을 만들어주는 생성자다.


이후 git명령어로 clone해온 폴더의 상위폴더에서 dir로 해당 폴더가 있는지 확인한 후, express-generator로 내용을 덮어씌울 예정이다. 아래와 같이 진행해주자.


express YOUR-APP-NAME // 이미 생성된 폴더의 이름과 같이 지정해주어야 덮어씌운다.

cd YOUR-APP-NAME // 생성된 어플리케이션의 폴더로 접근한다.

npm install // express가 자동생성한 npm의 설정대로 필요한 파일들을 설치한다.



모두 진행하고 나면 위와같은 화면이 나타난다. 이제 dir를 쳐보면 다양한 파일들이 있는걸 확인할 수 있다. 여기서 바로 heroku git으로 다시 deploy하면 그대로 진행 할 수 있겠지만.. 나는 github에 연동해보고 싶기때문에 아직 보류하도록한다.


3. github로 push하기


사실 이 작업을 진행하면서 느낀건, heroku 어플리케이션을 생성한 후 굳이 git clone을 해서 혼선을 만들지 않았어도 된다는 점인데.. 그 이유는 진행해보다보면 알 수 있다. 하지만 이 작업을 진행하면서 git에 대해서 약간 더 알게 될 수 있었기 때문에 나의 의식의 흐름;; 그대로 진행해보도록한다. 


먼저 github에 repository를 만들어주자. 이름은 상관없다 편한대로 만들고 따로 적어두기만 하면 오케이! 그 후 cmd 혹은 powershell로 위의 과정에서 만든 폴더로 접근한다음 다음과 같은 순서로 작업을 진행할 예정이다. 


// 현재 폴더에 저장된 git이 바라보고 있는 외부저장소 위치를 확인

// heroku git으로 등록되어있을 것이다.

git remote -v


// 외부저장소의 주소를 변경

git remote set-url origin YOUR-GIT-REPOSITORY.git


// 외부저장소 주소가 변경되었음을 확인

git remote -v


// 로컬의 git정보와 외부 저장소의 git정보가 서로 다르므로 합쳐준다.

git pull


// git에 커밋하고 push해준다.

git add .

git commit -sm 'first commit to github'

git push

// 이때, push되는 위치는 heroku git이 아닌 github repository이다.




전부 실행하면 사진과 같은 형태로 진행된다. 우리는 heroku 앱을 만들고,  heroku git에서 clone해왔으나 굳이 git의 정보를 변경해 github에 소스를 올려두었으므로 이제 github와 heroku를 연동해주어야 된다. 


4. github와 heroku 연동하기


먼저 http://www.heroku.com에서 로그인 한 후 만들어둔 heroku앱의 'Deploy'탭으로 가자. 그리고 Deployment Method영역의 github를 클릭 한 후 로그인하면, 해당 계정의 repository를 검색할 수 있다. 지정할 repository를 검색한 후 붉은 영역의 connect를 클릭하자.




connect를 클릭하면 아래같은 화면이 나오는데, 이 페이지에서 확인해야할 점은 두가지, Automatic Deploys(파란영역)와 Manual Deploys(초록영역)이다. 

 Automatic Deploys는 github의 저장소가 가진 여러 branch중 하나를 정해 브런치가 업데이트 될때마다 자동으로 heroku로 배포(deploy)하도록 도와준다. 다른 빌드할 필요없이 그냥 push만 해주면 되는 셈. 

 Manual Deploys는 저장소의 branch중 하나를 선택해 실시간으로 직접 배포하는 기능이다. 이미 우리는 github 저장소에 커밋하고 push까지 해주었으니 여기서 수동 배포를 해줘야 heroku 상에 반영됨을 확인 할 수 있을 것이다.



 직접 배포까지 완료하고 나면 Manual Deploys 영역의 하단에 'Your app was successfully deployed.'라는 메시지와 함께 'view'버튼이 활성화 되는데, 클릭하면 배포된 홈페이지로 들어가 볼 수 있다. 정말 친절하기 그지없는 heroku 사마.. 화면이 출력되면 "welcome to express"라는 express의 기본 문구가 반겨줄 것이다. 


이제 소스 관리 겸 외부 저장소에 연동하는 법까지 확인 했으니 다음엔 간단하게 뷰를 조작하고 외부 혹은 내부 DB로 연결하는 것을 확인해봐야되겠다. 

'DEV > MEAN' 카테고리의 다른 글

Heroku를 이용한 Node개발 : Heroku 앱 생성과 deploy  (1) 2015.08.23
Heroku를 이용한 Node개발 : 개요  (0) 2015.08.20




※본 글타래는 스프링 개발 강의보다 상세한 개발 일지 성격에 가까움을 미리 알립니다.

실수나 문제가 생겨 해결하는 경우도 있으니 글을 읽으며 따라하기보단 참고용으로 사용해주시면 감사하겠습니다.


<이전 항목 보기>

1. VirtualBox에 CentOS 7 설치하기

2. Virtualbox에서 SSH / FTP 연동하기




이번엔 톰캣을 설치하고 직접 접속해보는 단계까지만 진행할 예정이라 글이 짧을 것 같다. 우선, 이번에 필요한 파일은 Tomcat 8.5다. 9에서는 아직 여러 오류가 있다고해서 낮은 버전을 쓰기로 했다. 


다운로드 링크



링크에 들어가면 위 같은 화면이 뜰텐데, 여기서 Core에 있는 8.5.11버전의 64비트, tar.gz 파일로 다운 받았다. 이제 이 파일을 파일질라에서 가상 머신으로 옮겨주자. 이번에 올릴 파일은 /usr/local/ 경로에 업로드 해주자. 위치는 사실 크게 문제는 없으나 찾아보니 저 경로를 제일 많이 쓰는듯 .. 


(덧. 저번 글에서 gomja 계정에서 usr라는 폴더를 만들어 파일을 올렸는데, 경로를 더 위쪽으로 파고 들어가니 usr라는 폴더가 따로 있더라 .. 리눅스 바보의 폐해 .. 이번엔 제대로된 경로에 설치하기로 했다.)


업로드한 후 쉘로 접근해 파일이 제대로 업로드 되어있는지 확인해보자 



화면과 같이 파일이 있는게 확인된다면 아래 명령어를 입력해서 파일 압축을 풀어주자 


tar -zxvf apache-tomcat-8.5.11.tar.gz


"-zxvf"가 tar.gz파일을 압축해제하는 명령어라고 한다. 그리고 'a'까지만 입력하고 탭을 누르면 알아서 이름을 채워넣더라. 리눅스가 생소하니 이런 작은 팁에도 깜짝깜짝 놀랬다... 민망.. 압축을 풀어준 후 압축 푼 경로로 이동해보자


cd apache-tomcat-8.5.11.tar.gz/bin      // 압축 푼 폴더의 bin 폴더로 이동한다

./startup.sh                            // 톰캣을 실행한다.

위와 같이 입력했을때 스크린샷 같은 화면이 나타난다면 실행은 성공




이제 호스트 PC에서 간단히 브라우저를 열어서 http://가상PC-IP:8080 을 입력해 접근해보자. 만약 이때 톰캣은 정상적으로 실행되는데 접근이 되지 않는다면 방화벽에서 8080포트를 풀어줘야한다. (한국어 기준으로)프로그램 - 잡다 - 방화벽 창을 열어주자 


 

방화벽 설정 페이지를 띄우면 위와같은 화면이 될텐데, '설정'부분의 "런타임"을 "영구"로 우선 바꿔주자. 런타임에만 설정해두면 포트를 열어둔게 재부팅 할때마다 날아가버리게 된다. 영구로 바꾼 뒤에는 '포트'탭을 클릭하고 '추가' 버튼을 눌러주자



추가한 뒤에는 8080포트를 열어주고 확인 버튼을 누르자. 이제 톰캣에서 사용하는 8080포트를 열어줬고, 혹시모르니 리부팅해 다시 톰캣을 실행하고 호스트 PC에서 http://아이피:8080으로 접근해보자



위 같이 입력하고 호스트 PC에서 고양이가 그려진 페이지까지 정상적으로 뜬다면 톰캣까지 설치완료! 이제 STS로 스프링 프로젝트를 만들고 가상 머신에 올리는 단계로 진행해야될 것 같다.




※본 글타래는 스프링 개발 강의보다 상세한 개발 일지 성격에 가까움을 미리 알립니다.

실수나 문제가 생겨 해결하는 경우도 있으니 글을 읽으며 따라하기보단 참고용으로 사용해주시면 감사하겠습니다.


<이전 항목 보기>


1. VirtualBox에 CentOS 7 설치하기




OS 설치가 끝났으니 서버로 사용하기 위한 기본 세팅들을 진행해봤다. 이번에 진행할 작업은 아래와 같다.

 

1. VirtualBox의 가상 머신과 PC 네트워크 설정

2. SSH 연결 확인

3. FTP 연결 확인 및 JAVA 설치



1. VirtualBox 가상 머신과 PC 네트워크 연결


 가상 머신을 실제 서버로 사용하기 위해서 내 PC와 가상머신의 네트워크 연결이 확인되어야 할텐데, 찾아보니 많이 어렵지 않았다. 먼저 VirtualBox 가상 머신의 설정으로 가보자 




여기서 'NAT'으로 설정되어있는 어댑터1은 두고, 어댑터2로 이동해 "네트워크 어댑터 사용하기"를 클릭하고 브리지 어댑터로 설정해주자. 브리지 어댑터로 설정하면 호스트 PC와 같은 레벨에서 새로운 네트워크를 생성하는 것과 같은 효과라고.. 이렇게 설정해주고 OK를 누른 후 가상 머신을 실행해주자. 





가상머신을 실행한 후 오른쪽 위 구석 버튼을 눌러 이더넷이 연결되어있는지 둘다 연결되어 있는지 확인하고 연결되어있지 않다면 켜기 버튼을 눌러주면 된다. 들어온 김에 SSH 접속을 위해 IP까지 확인해보자. 터미널로 간 후 아래와 같이 입력해주자 


ip addr show



여기서 브리지 어댑터로 생성된 이더넷의 IP를 확인할 수 있다. 검은색 부분에 아이피가 보이게 된다. 이 IP를 잘 적어두자 


2. SSH 연결 확인하기


네트워크를 연결했으니 이제 호스트 PC에서 SSH를 사용해보자. SSH 연결 확인은 정말 쉽다. 지금부터 필요한 준비물은 PuTTY와 FileZilla. PuTTY는 SSH에 FileZilla는 FTP연결에 사용할 예정이다. 


PuTTY 다운로드 링크

FileZilla 다운로드 링크


PuTTY를 다운받은 후 실행해 아까 적어둔 IP를 입력해주자




자주 들락날락하게 될테니 Save버튼을 눌러 지금 세션을 저장해주고, Open을 눌러 연결했을때 아래와 같은 화면이 나타나고, 로그인까지 성공한다면 여기까지는 성공! 




3. FTP 연결과 JAVA 설치 


사실 CentOS를 GUI로 설치했으니 JAVA설치로 하지 않아도 될 것으로 보이는데.. 그래도 앞으로 SSH를 사용할 일이 더 많을 것으로 보이니 직접 FTP로 업로드 후 설치해보자.


먼저 JDK를 호스트 PC에서 다운받아보자. 여기서는 'Java SE Development Kit 8u121'를 사용했고 rpm 파일로 다운받았다.


JDK 다운로드 링크 


다운받은 뒤에는 FileZilla를 설치 후 실행해주자. 가상 머신으로 접속하기 위해서는 '호스트' 부분에 stfp://아이피 , 그리고 계정명과 패스워드를 입력해준 후 빠른 연결 하면 된다. 



오른쪽에 디렉토리 목록이 뜬다면 제대로 연결된 상태다. 이제 여기에 간단히 'usr'라는 폴더를 하나 만들어주고 거기에 다운받은 jdk rpm 파일을 이동시켜주자. 그 후 PuTTY에서 다음과 같이 입력해보자 


cd usr // 생성한 usr 폴더로 이동한다.

ls     // 파일 목록을 확인한다. 아래의 rpm 파일이 있는지 확인해보자

rpm -ivh jdk-8u121-linux-x64.rpm    // rpm 명령어로 jdk 설치 시작!

위와 같이 쉘에서 입력하면 




이렇게 JDK 설치가 진행된다. 설치가 완료되고 나면 


java -version


위의 명령을 입력해 제대로 JDK가 설치되어있는지 확인해보도록 하자. 이왕 설치까지 한 김에 간단하게 HelloWorld를 한번 띄워보면 더 정확할 것 같다. 

vi를 사용하는 법을 잘 모르겠다면 간단하게 코드를 짜서 보내보자. 나는 'HelloWorld.java'파일을 메모장으로 아래같이 작성해서 /usr 디렉토리에 FTP 전송하였다.


public class HelloWorld

{

public static void main(String args[])

{

System.out.println("Hello Funking World");

}

}


문구는 각자 원하는 취향대로 적당히 적어주고 FTP로 전송한 뒤, 셀에서 아래와 같이 입력했다. 


javac HelloWorld.java

java HelloWorld


이렇게 입력했을때 아래 화면 같이 제대로 컴파일 후 실행까지 된다면, JDK 설치까지 모두 완료한 것이다. 왠지 자바로 좀 길이 샌거같지만 확실한 확인 법이니까 그냥 넘어가는 걸로 





참고 사이트 : http://roqkffhwk.tistory.com/99

'DEV > LINUX' 카테고리의 다른 글

[CENTOS]CentOS 7에 Tomcat 8 설치하기  (1) 2017.03.07
[CentOS] VirtualBox로 CentOS7 설치하기  (0) 2017.03.04

+ Recent posts