2021. 8. 3. 10:27ㆍ문제들
CORS error (ajax-xml-servlet/jsp)
발단
공공 데이터 포털 https://data.go.kr/ 에서 공휴일 정보 ( XML ) 를 REST로 받으려고 했다. 남들처럼 단순하게 XMLHttpRequest w3schools 소스로 연습하려고 했고, 환경은 apache/tomcat, jsp/servlet 에서 간단히 예제 연습하려 했다.
하지만 뜨라는 결과는 나오지 않고 CORS 에러 만 콘솔창에 떴다.
Access to XMLHttpRequest at '외부주소' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Cross Origin Resource Sharing....
CORS 에 대한 설명은
을 보면 된다.
해당 글을 보고 내 나름대로 소화한 내용은....
Javascript 단에서 ajax를 사용하면 client 단에서 다른 server로 비동기 요청을 보내게 된다.
- SOP(Same Origin Policy) / CORS 라는 정책이 있다. 둘은 별개다.
- SOP : 어지간하면 느그 Origin 에서만 요청해서 써라
- origin : protocol + domain + port(애매)
- CORS : 다른 Origin에 요청해도 되는데 선은 지켜라
Access-Control-Allow-Origin
: request header. 허용한 Origin 이다. * 이면 다 허용해준다.- 설정할때 요청할 특정 Origin을 정해주는게 낫다.
- SOP : 어지간하면 느그 Origin 에서만 요청해서 써라
- preflight : 미리 요청가능할지 말지 간을 보는 작업
- 두 정책은 왜 나왔는가? :
- 결국 웹 어플리케이션들 간의 소통 (web client - another web server )
- 어플리케이션의 소통을 아무거나 허용하면 위험하다
- XSS 를 위시한 사용자의 공격에 취약
- 그럼에도 요청이 필요한 상황이 있다
- 그래서 등록한 Origin만 요청하는것을 허용한다.
이 정도다.
저분 글은 두고두고 살펴 볼 생각이다.
삽질
되는거 안되는거 다 적용해 보았다.
나중에 비슷한 문제 생기면 일단 이거부터 시도해보고 하련다. 하다가 하나는 걸리겠지
header 추가
https://jang8584.tistory.com/250
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
해당 코드를 custom filter 를 만들어서 집어넣는다.
안된다.
var xhttp, xmlDoc, txt, x, i;
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
xmlDoc = this.responseXML;
txt = xmlDoc;
document.getElementById("demo").innerHTML = txt;
}
};
xhttp.open("GET","요청하고싶은 주소",true);
XMLHttpRequest open 후에 추가해 보았다.
잘 안되었다.
서버단에서 설정
참고로 서버 단에서 cors를 지원하는 것은 상당히 간단한 것으로 알고 있습니다.
http://enable-cors.org/index.html 사이트를 참고하시기 바랍니다.
(아파치 서버 cors 지원 설정법 http://enable-cors.org/server_apache.html)
나는 apache/ tomcat 이라
- apache - https://enable-cors.org/server_apache.html
- tomcat - https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter
여기 기웃거려가며 프로젝트 내 web.xml 에
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
추가했다.
그래도 안된다.
JSONP - callback padding
나중에 참고할 링크
살펴본 링크
https://m.blog.naver.com/musasin84/60208652179
https://stove99.tistory.com/10
https://pythonq.com/so/jquery/1976603
// find some demo xml - DuckDuckGo is great for this
var xmlSource = "http://api.duckduckgo.com/?q=StackOverflow&format=xml"
// build the yql query. Could be just a string - I think join makes easier reading
var yqlURL = [
"http://query.yahooapis.com/v1/public/yql",
"?q=" + encodeURIComponent("select * from xml where url='" + xmlSource + "'"),
"&format=xml&callback=?"
].join("");
// Now do the AJAX heavy lifting
$.getJSON(yqlURL, function(data){
xmlContent = $(data.results[0]);
var Abstract = $(xmlContent).find("Abstract").text();
console.log(Abstract);
});
안됨
https://jang8584.tistory.com/250
$.ajax({
url : "http://127.0.0.1:8080/server/data.jsp",
dataType : "jsonp",
jsonp : "callback",
success : function(d){
// d.key;
},
error : function(xhr){
console.log('실패 - '+xhr);
}
안됨 ( 하지만 이분 링크는 참고)
<script>
var url= 'https://rss.blog.naver.com/yohanlee0602.xml';
$.ajax({
type: 'GET',
url: "https://api.rss2json.com/v1/api.json?rss_url=" + url,
dataType: 'jsonp',
success: function(data) {
console.log(data.feed.description);
console.log(data);
}
});
</script>
안됨
https://luckybaby.tistory.com/720
이분 것은 시도 안해보았으나 변환 하는것은 흥미로워 보인다.
해결
proxy 생성 & 서버간 통신
- https://data.go.kr/bbs/faq/selectFaqList.do - FAQ
- OpenAPI를 js를 이용하여 화면에 노출하려 하였으나 호출 결과 Cross Domain 에러가 발생하고 있습니다. 원인과 해결 방법을 알고 싶습니다.
Cross Domain은 Same-Origin Policy[동일근원정책]으로
Javascript에서 Ajax 사용 시 사용 문서와 동일한 도메인으로만 데이터 요청 및 전송이 가능하도록 하는 보안 정책입니다.안녕하세요. 통계지리정보서비스를 이용해 주셔서 감사합니다.
서버쪽에는 Cross Domain 문제를 해결하기 위한 코드를 다 넣어 놓은 상태입니다.
그럼에도 클라이언트단에서 충돌이 발생한다면 브라우저 문제이므로
Ajax를 Jsonp로 구성하거나 Proxy를 만들어 서버간 통신하는 방식으로 해결하셔야 합니다.
sample 예제
client side Ajax
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title> openAPI 통계청:공간정보서비스과 </title>
<script src=http://sgis.kostat.go.kr/OpenAPI2/Key.do?serviceKey=발급받은키" type="text/javascript" ></script>
<script type="text/javascript">
var userURL = "http://사용자페이지URL";
function fncGeoCode() {
var url = userURL + "/AjaxRequest.jsp?getUrl=";
var subURL = "http://sgis.kostat.go.kr/OpenAPI2/geocoder.do?serviceKey="+ document.getElementById("serviceKey").value;
subURL += "&type=2";
subURL += "&sido="+encodeURIComponent(document.getElementById("sido").value);
subURL += "&sigungu="+encodeURIComponent(document.getElementById("sigungu").value);
subURL += "&dong="+encodeURIComponent(document.getElementById("dong").value);
subURL += "&jibun="+document.getElementById("jibun").value;
url += encodeURIComponent(subURL);
$.ajax({
"url" : url,
"type" : "GET",
"success" : function(result) {
if(result == null || result == ""){
alert("해당 주소로 얻을수 있는 좌표가 없습니다. 주소값을 다시 입력하세요");
}else{
$.each(result, function(i,value){
if(result.data == null ){
if(i==0){
$("#x_coords").attr("value",value.posX);
$("#y_coords").attr("value",value.posY);
$("#address").attr("value", value.address);
}
}
});
}
},
"async" : "false",
"dataType" : "json",
"error": function(x,o,e){
alert(x.status + ":" +o+":"+e);
}
});
}
</script>
</head>
<body >
serviceKey : <input type="text" id="serviceKey" value="발급받은키"/><br />
지번주소<br />
시도 : <input type="text" id="sido" value="대전광역시"/><br />
시군구 : <input type="text" id="sigungu" value="서구"/><br />
읍면동 : <input type="text" id="dong" value="월평동"/><br />
지번 : <input type="text" id="jibun" value="245"/><br />
<input type="button" value="GeoCode Service" onclick="fncGeoCode()"/><br />
중부원점(TM_M)<br />
X좌표 : <input type="text" id="x_coords" /> Y좌표 : <input type="text" id="y_coords" /><br />
주 소 : <input type="text" id="address" /><br />
</body>
</html>
Server- side Request
<%@ page language="java" import="java.io.*,java.net.*" contentType="text/xml; charset=utf-8"
pageEncoding="utf-8"%>
<%
URL url = new URL(request.getParameter("getUrl"));
URLConnection connection = url.openConnection();
connection.setRequestProperty("CONTENT-TYPE","text/html");
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(),"utf-8"));
String inputLine;
String buffer = "";
while ((inputLine = in.readLine()) != null){
buffer += inputLine.trim();
}
System.out.println("buffer : " + buffer);
in.close();
%><%=buffer%>
잘 돌아갈 뻔 했다. 하지만 이것만으로는 바로 적용이 불가능 했다.
URL 통한 XML 정보 받기
https://stackoverflow.com/questions/33759608/getting-xml-from-url-with-httpurlconnection-in-android
이 둘을 잘 조합해서 만들었다.
code
Ajax Request ( Client -> Origin(Proxy)) - 모범음식점 검색
server side 로 요청을 toss 한다. 짬 때리는 직장인의 완벽한 표본이다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
</style>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>OPEN API TEST</title>
<script type="text/javascript"
src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script type="text/javascript">
// 입력값 정수인지 판별
function isNumeric(str) {
if (typeof str != "string") return false // we only process strings!
return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
!isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
}
var userURL = "http://localhost:8080/apitest";
// 공휴일 검색 기능
function fnHoliday() {
var month = document.getElementById("month").value.padStart(2,'0');
var url = userURL + "/AjaxRequest.jsp?getUrl=";
var subURL = "http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo?solYear="
+ document.getElementById("year").value;
subURL += "&solMonth=" + month;
subURL += "&ServiceKey=" + document.getElementById("serviceKey").value;
url += encodeURIComponent(subURL);
console.log(url);
$.ajax({
"url" : url,
"type" : "GET",
"success" : function(result) {
$('#result').innerHTML = result;
console.log("success : " + result.responseXML);
console.log(result);
x = result.getElementsByTagName("resultMsg")[0];
console.log(x);
console.log(x.childNodes[0].nodeValue);
var items = result.getElementsByTagName("item");
var txt = "";
// 테이블 구성
if (result == null || result == "") {
alert("해당 주소로 얻을수 있는 좌표가 없습니다. 주소값을 다시 입력하세요");
} else {
$.each(items, function(i, value) {
if (i == 0) {
txt += "<tr>";
txt += "<th>날짜</th>";
txt += "<th>명칭</th>";
txt += "<th>종류</th>";
txt += "<th>공공기관 휴일 여부</th>";
txt += "</tr>";
}
if (result.data == null) {
}
txt += "<tr>";
txt += "<th>"+value.getElementsByTagName("locdate")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("dateName")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("dateKind")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("isHoliday")[0].childNodes[0].nodeValue+"</th>";
txt += "</tr>";
});
document.getElementById("result").innerHTML = txt;
}
},
"async" : "true",
"dataType" : "xml",
"error" : function(x, o, e) {
$('#result').innerHTML = x.responseText;
console.log(x.responseText);
console.log(x);
//alert(x.status + ":" + o + ":" + e);
}
});
}
// 송파구 모범 음식점 목록 기능
function fnRestaurant() {
// 입력값 처리 - default 10개
var num = document.getElementById("count").value;
var limit = 10;
if(isNumeric(num)){
limit = parseInt(num,10);
}
var month = document.getElementById("month").value.padStart(2,'0');
var url = userURL + "/AjaxRequest.jsp?getUrl=";
var subURL = "http://openAPI.songpa.seoul.kr:8088";
subURL += "/71456d5164666f753539587572624f";
subURL += "/xml";
subURL += "/SpModelRestaurantDesignate";
subURL += "/1";
subURL += "/"+ limit + "/";
url += encodeURIComponent(subURL);
console.log(url);
$.ajax({
"url" : url,
"type" : "GET",
"success" : function(result) {
$('#result').innerHTML = result;
x = result.getElementsByTagName("SpModelRestaurantDesignate")[0];
document.getElementById('totalCount').innerHTML = x.getElementsByTagName("list_total_count")[0].childNodes[0].nodeValue;
var rows = result.getElementsByTagName("row");
var txt = "";
if (result == null || result == "") {
alert("해당 주소로 얻을수 있는 좌표가 없습니다. 주소값을 다시 입력하세요");
} else {
// 테이블 구성
$.each(rows, function(i, value) {
if (i == 0) {
// 헤더 구성
txt += "<tr>";
txt += "<th>시군구코드</th>";
txt += "<th>지정년도</th>";
txt += "<th>지정번호</th>";
txt += "<th>신청일자</th>";
txt += "<th>지정일자</th>";
txt += "<th>업소명</th>";
txt += "<th>소재지도로명</th>";
txt += "<th>소재지지번</th>";
txt += "<th>허가(신고)번호</th>";
txt += "<th>업태명</th>";
txt += "<th>주된음식</th>";
txt += "<th>영업장면적(㎡)</th>";
txt += "<th>행정동명</th>";
txt += "<th>급수시설구분</th>";
txt += "<th>소재지전화번호</th>";
txt += "</tr>";
}
// 테이블 내용 구성
txt += "<tr>";
txt += "<th>"+value.getElementsByTagName("CGG_CODE")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("ASGN_YY")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("ASGN_SNO")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("APPL_YMD")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("ASGN_YMD")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("UPSO_NM")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("SITE_ADDR_RD")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("SITE_ADDR")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("PERM_NT_NO")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("SNT_UPTAE_NM")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("MAIN_EDF")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("TRDP_AREA")[0].childNodes[0].nodeValue+"</th>";
txt += "<th>"+value.getElementsByTagName("ADMDNG_NM")[0].childNodes[0].nodeValue+"</th>";
// 값이 존재하지 않을 때 예외 처리
var isFacility = value.getElementsByTagName("GRADE_FACIL_GBN")[0].childNodes[0];
//console.log(isFacility);
var isFacilityVal = typeof isFacility != "undefined" ? isFacility.nodeValue : "없음";
txt += "<th>"+isFacilityVal+"</th>";
var isTelNo = value.getElementsByTagName("UPSO_SITE_TELNO")[0].childNodes[0];
var isTelNoVal = typeof isTelNo != "undefined" ? isTelNo.nodeValue : "미기재";
txt += "<th>"+isTelNoVal+"</th>";
txt += "</tr>";
});
document.getElementById("restaurantResult").innerHTML = txt;
}
},
"async" : "true",
"dataType" : "xml",
"error" : function(x, o, e) {
$('#result').innerHTML = x.responseText;
console.log(x.responseText);
console.log(x);
}
});
}
</script>
</head>
<body>
<h1>(공공데이터포털)년,월을 입력해서 공휴일을 확인</h1>
<input type="hidden" id="serviceKey"
value="써어비스키" />
<br /> 년 :
<input type="text" id="year" value="" placeholder="연도 입력(예:2019)"/>
<br /> 월 :
<input type="text" id="month" value="" placeholder="월 입력(예:3)"/>
<br />
<input type="button" value="공휴일 검색" onclick="fnHoliday()" />
<table id='result'></table>
<h1>(서울열린데이터광장API)송파구 모범음식점 목록</h1>
<br /> 출력할 음식점 개수 :
<input type="text" id="count" value="" placeholder="음식점 개수 입력(예:3)"/>
<br />
<input type="button" value="음식점 목록 가져오기" onclick="fnRestaurant()" />
<br/>
<span>송파구 내 총 음식점<h3 id='totalCount'></h3></span>
<br/>
<table id='restaurantResult'></table>
<br />
</body>
</html>
Server side Request( Proxy -> another Origin)
그리 큰 차이는 없다. 마지막 부분에 buffer를 out.print 로 출력한다는 것이 소소하게 다른점.
<%@ page language="java" import="java.net.*, java.io.*" pageEncoding="UTF-8"%><%
URL url = new URL(request.getParameter("getUrl"));
URLConnection connection = url.openConnection();
connection.setRequestProperty("CONTENT-TYPE", "text/plain");
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
String inputLine;
String buffer = "";
while ((inputLine = in.readLine()) != null) {
buffer += inputLine.trim();
}
//System.out.println("buffer : " + buffer);
in.close();
response.setContentType("application/xml");
out.print(buffer);
%>
그외 발생한 자잘한 것들
- Uncaught ReferenceError: $ is not defined (ajax)
- script tag를 열면 닫는 태그도 똑같이 넣어준다.
<script ~~~ ></script>
- JQuery - $ is not defined
- ReferenceError: $ is not defined (ajax)
- jquery 를 추가해준다.
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
- 200 parserrror invalid
- dataType 확인
- 내 경우엔 xml 이라서 xml 로 설정
- dataType 확인
- XML declaration allowed only at the start of the document
- https://finsternis.tistory.com/613
- https://ticast.tistory.com/22
- 띄어쓰기 문제이다.
- 태그 태그 사이를 줄바꿈 하지 말고 붙인다
<%@ page trimDirectiveWhitespaces="true" %>
을 붙인다.
happy 하다
'문제들' 카테고리의 다른 글
버튼을 눌렀을 때 새로고침이 되는 경우를 해결해보자 (0) | 2021.09.02 |
---|---|
mysql port 3306 문제 (0) | 2021.08.20 |
python module not found (경로 참조 문제) (0) | 2021.06.10 |
AttributeError: module 'tensorflow' has no attribute 'contrib' (0) | 2021.05.25 |
[JavaScript] forEach is not a function error (0) | 2021.03.26 |