[백업][가리사니] xml Rpc 블로그 : 5. java로 구현해보자
java, protocols, xml
이 문서는 가리사니 개발자 포럼에 올렸던 글의 백업 파일입니다. 오래된 문서가 많아 현재 상황과 맞지 않을 수 있습니다.
XML-RPC 블로그 시리즈
Apache XML-RPC 를 이용하는 방법
- 사실 너무많은 파일이 필요해서 필자는 직접 구현을 더 추천합니다.
- 직접구현은 아래에 있습니다.
- 클라이언트 기능만 쓰지만, 서로 연관된것들이 많아 추가 후 필요없는 기능들을 제거하시면 될 것 같습니다. pom.xml 추가
<dependency>
<groupId>org.apache.xmlrpc</groupId>
<artifactId>xmlrpc-common</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.xmlrpc</groupId>
<artifactId>xmlrpc-client</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.xmlrpc</groupId>
<artifactId>xmlrpc-server</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.ws.commons.util</groupId>
<artifactId>ws-commons-util</artifactId>
<version>1.0.2</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
글 등록 구현 예제
XmlRpcClient blog = new XmlRpcClient();
XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
Hashtable<String, String> post = new Hashtable<>();
Vector<Serializable> params = new Vector<Serializable>();
// 예를들어 네이버 기준으로 https://api.blog.naver.com/xmlrpc
config.setServerURL(new URL("XML RPC 주소"));
config.setBasicEncoding("utf-8");
config.setEncoding("utf-8");
// 포스트 등록글 참고바람.
// 네이버 같은경우 블로그 ID나 서비스 ID나 그냥 ID 입니다.
params.addElement("블로그 ID");
params.addElement("서비스 ID");
params.addElement("API 키");
// 각종 컨텐츠 구조체(포스트 등록 참고)
post.put("title", "타이틀");
post.put("description", "컨텐츠<b>asdf</b>asdf");
post.put("tags", "가리사니,개발자,공간");
post.put("categories", "프로그래밍");
params.addElement(post);
// 숨김여부
params.addElement(true);
// 성공시 포스트 ID가 리턴됩니다.
// 실패시 Exception 으로 오류를 알려줍니다.
String blogPostID = (String) blog.execute(config, "metaWeblog.newPost", params);
직접구현
XML RPC 클라이언트 객체
- 급하게 대충짠거라 소스가 엉망입니다. 하하하..;;
- 인코딩은 [http://www.htmlescape.net/htmlescape_for_java.html 참고해보세요.
- 다만 위 사이트 소스는 의도된건지 버그인지 모르겠지만 < 가 있으면 < -> < 로 연속으로 분해됨으로,
- & 를 먼저 처리하게 수정하시기 바랍니다. 그냥 이소스 그대로 쓰셔도됩니다....
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* XmlRpcClient<br>
* 2016-07-03 전명 박용서 : 작성<br>
* 급하게 짠거라 리팩토링해서 쓰세요..
*/
public class XmlRpcClient
{
/** 커넥션 */
private URLConnection connection;
/** 요청 */
private Writer request;
/** 생성자 : newInstance 사용 */
private XmlRpcClient() {}
/**
* 연결을 합니다.<br>
* SSL/TLS 인증에 문제가 있는 경우에만 여기서 수동으로 사용하길 바라며,
* 그렇지않은경우 newInstance(URL url, String method) 를 사용하기 바랍니다.
* @param url
* @param sslSocketFactory
* @param method
* @return
* @throws IOException
*/
public static XmlRpcClient newInstance(URL url, SSLSocketFactory sslSocketFactory, String method)
throws IOException
{
// 연결
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 커스텀 보안인증
if (sslSocketFactory != null && "https".equals(url.getProtocol()))
{
((HttpsURLConnection)connection).setSSLSocketFactory(sslSocketFactory);
}
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
// 헤더들을 지워주지 않으면 서버에 따라서 인식을 못함.
connection.setRequestProperty("User-Agent", "");
connection.setRequestProperty("Accept", "");
connection.setRequestProperty("Content-type", "");
connection.connect();
// 연결
XmlRpcClient xmlRpcClient = new XmlRpcClient();
xmlRpcClient.connection = connection;
xmlRpcClient.request = new OutputStreamWriter(connection.getOutputStream(), "utf-8");
// 기본문입력
xmlRpcClient.request.write("<methodCall><methodName>");
xmlRpcClient.request.write(method);
xmlRpcClient.request.write("</methodName><params>");
return xmlRpcClient;
}
/**
* 연결을 합니다.
* @param url
* @param method
* @return
* @throws IOException
*/
public static XmlRpcClient newInstance(URL url, String method)
throws IOException
{
return newInstance(url, null, method);
}
/**
* XML 이스케이프
* @param out
* @param value
* @throws IOException
*/
private static void encodeXmlEscape(Writer out, String value) throws IOException
{
char[] chars = value.toCharArray();
for (char c : chars)
{
switch (c)
{
case '&' : out.write("&"); break;
case '<' : out.write("<"); break;
case '>' : out.write(">"); break;
default : out.write(c);
}
}
}
/**
* 타입을 지정하는 파라미터를 추가합니다.
* @param type
* @param value
* @return
* @throws IOException
*/
public XmlRpcClient addParam(String type, String value) throws IOException
{
request.write("<param><value><");
request.write(type);
request.write('>');
encodeXmlEscape(request, value);
request.write("</");
request.write(type);
request.write("></value></param>");
return this;
}
/**
* 파라미터를 추가합니다.<br>
* 별도 데이터 타입을 넣지 않습니다.
* @param value
* @return
* @throws IOException
*/
public XmlRpcClient addParam(String value) throws IOException
{
request.write("<param><value>");
encodeXmlEscape(request, value);
request.write("</value></param>");
return this;
}
/**
* string 타입의 파라미터를 추가합니다.
* @param value
* @return
* @throws IOException
*/
public XmlRpcClient addParamString(String value) throws IOException
{
return addParam("string", value);
}
/**
* int 타입의 파라미터를 추가합니다.
* @param value
* @return
* @throws IOException
*/
public XmlRpcClient addParamInt(int value) throws IOException
{
return addParam("int", Integer.toString(value));
}
/**
* boolean 타입의 파라미터를 추가합니다.
* @param value
* @return
* @throws IOException
*/
public XmlRpcClient addParamBoolean(boolean value) throws IOException
{
return addParam("boolean", value ? "1" : "0");
}
/**
* struct 타입의 파라미터를 추가합니다.
* @param struct
* @return
* @throws IOException
*/
public XmlRpcClient addParamStruct(XmlRpcStruct struct) throws IOException
{
request.write("<param><value>");
struct.out(request);
request.write("</value></param>");
return this;
}
/**
* 최종 결과를 가져옵니다.<br>
* <b>주의 : </b><br>
* XmlRpcClient 객체는 send 를 부름으로써 끝나게 됩니다.<br>
* 즉, 이 객체를 다시 재활용할 수 없습니다.<br>
* 새롭게 newInstance 하시기 바랍니다.
* @return
* @throws IOException
* @throws SAXException
* @throws ParserConfigurationException
*/
public XmlRpcResult send() throws IOException, SAXException, ParserConfigurationException
{
// 보내기 끝 / 닫기
request.write("</params></methodCall>");
request.flush();
request.close();
// 반환
try (InputStream is = connection.getInputStream())
{
return new XmlRpcResult(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is));
}
catch (IOException e)
{
throw e;
}
}
/**
* XmlRpcStruct 객체
*/
public static class XmlRpcStruct
{
private Writer data = new StringWriter();
/**
* 만들어진 결과를 wirter 를 통해 출력합니다.
* @param writer
* @throws IOException
*/
private void out(Writer out) throws IOException
{
out.write("<struct>");
out.write(((StringWriter)data).toString());
out.write("</struct>");
}
/**
* 맴버추가
* @param name
* @param type
* @param value
* @return
* @throws IOException
*/
public XmlRpcStruct addMember(String name, String type, String value) throws IOException
{
data.write("<member><name>");
encodeXmlEscape(data, name);
data.write("</name><value><");
data.write(type);
data.write('>');
encodeXmlEscape(data, value);
data.write("</");
data.write(type);
data.write("></value></member>");
return this;
}
/**
* 맴버추가
* @param name
* @param value
* @return
* @throws IOException
*/
public XmlRpcStruct addMember(String name, String value) throws IOException
{
data.write("<member><name>");
encodeXmlEscape(data, name);
data.write("</name><value>");
encodeXmlEscape(data, value);
data.write("</value></member>");
return this;
}
/**
* 맴버추가
* @param name
* @param type
* @param value
* @return
* @throws IOException
*/
public XmlRpcStruct addMemberArray(String name, String type, String... values) throws IOException
{
data.write("<member><name>");
encodeXmlEscape(data, name);
data.write("</name><value><array><data>");
for (String value : values)
{
data.write("<value><");
data.write(type);
data.write('>');
encodeXmlEscape(data, value);
data.write("</");
data.write(type);
data.write("></value>");
}
data.write("</data></array></value></member>");
return this;
}
/**
* 맴버추가
* @param name
* @param value
* @return
* @throws IOException
*/
public XmlRpcStruct addMemberString(String name, String value) throws IOException
{
return addMember(name, "string", value);
}
/**
* 맴버추가
* @param name
* @param value
* @return
* @throws IOException
*/
public XmlRpcStruct addMemberStringArray(String name, String... value) throws IOException
{
return addMemberArray(name, "string", value);
}
}
/**
* XmlRpcResult
*/
public static class XmlRpcResult
{
private final Document dom;
private final boolean success;
private String result;
private Map<String, String> fault;
/** 직접 생성 불가 */
private XmlRpcResult(Document dom)
{
this.dom = dom;
// 성공
if ( (this.success = (this.dom.getElementsByTagName("fault").getLength() == 0)) )
{
NodeList nl = this.dom.getElementsByTagName("value");
if (nl.getLength() != 0)
{
Node node = nl.item(0);
if (node.hasChildNodes())
{
result = node.getChildNodes().item(0).getTextContent();
}
else
{
result = node.getTextContent();
}
}
}
// 실패
else
{
NodeList nl = this.dom.getElementsByTagName("member");
fault = new HashMap<>();
for (int i = 0 ; i < nl.getLength() ; i++)
{
Node node = nl.item(i);
NodeList member = node.getChildNodes();
String name = null, value = null;
for (int j = 0 ; j < member.getLength() ; j++)
{
Node attr = member.item(j);
switch (attr.getNodeName())
{
case "name" : name = attr.getTextContent(); break;
case "value" : value = attr.getTextContent(); break;
}
}
if (name != null && value != null)
{
fault.put(name.toLowerCase(), value);
}
}
}
}
/**
* 최종 결과 xml을 가져옵니다.<br>
* 나중에 dom을 복사하지못하도록 방어적 복사복 추가 바람.
* @return
*/
public Document getResultDocument()
{
return this.dom;
}
/**
* 성공여부를 가져옵니다.
* @return
*/
public boolean isSuccess()
{
return success;
}
/**
* 성공 결과를 가져옵니다.<br>
* 성공하지 못한경우는 null이 반환됩니다.
* @return
*/
public String getResult()
{
return result;
}
/**
* 실패사유를 가져옵니다.
* @return
*/
public String getFaultString()
{
if (!isSuccess())
{
return fault.get("faultstring");
}
return null;
}
/**
* 실패 코드를 가져옵니다.
* @return
*/
public String getFaultCode()
{
if (!isSuccess())
{
return fault.get("faultcode");
}
return null;
}
/**
* 스트링 출력<br>
* toString을 오버라이드 하지 않는 이유는
* try - catch를 예외 처리하는 것보다 별도로 작성하는게 낫다고 생각.
* @throws TransformerFactoryConfigurationError
* @throws TransformerException
*/
public String getResultDocumentString() throws TransformerFactoryConfigurationError, TransformerException
{
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(this.dom);
transformer.transform(source, result);
return result.getWriter().toString();
}
}
}
사용하기
// 각종정보
String id = "블로그 ID";
String key = "API 키";
URL url = new URL("XML RPC 주소");
// 글쓰기
XmlRpcClient blog = XmlRpcClient.newInstance(url, "metaWeblog.newPost");
blog.addParamString(id);
blog.addParamString(id);
blog.addParamString(key);
blog.addParamStruct
(
new XmlRpcStruct()
.addMemberString("title", "가리사니 개발자공간")
.addMemberStringArray("categories", "프로그래밍")
.addMemberString("description", "<h1>내용입니다.</h1>가나다라")
// 태그 : 네이버
.addMemberString("tags", "태그1, 태그2")
// 태그 : 티스토리
.addMemberStringArray("mt_keywords", "태그1", "태그2")
);
blog.addParamBoolean(true);
XmlRpcResult result = blog.send();
if (result.isSuccess())
{
System.out.println("성공");
System.out.println(result.getResult());
System.out.println(result.getResultDocumentString());
}
else
{
System.out.println("실패");
System.out.println(result.getFaultCode());
System.out.println(result.getFaultString());
System.out.println(result.getResultDocumentString());
}
성공했다면 아래와 같은 출력이 나옵니다.
성공
[[포스트 번호]]
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<methodResponse>
<params>
<param>
<value>[[포스트 번호]]</value>
</param>
</params>
</methodResponse>