아키텍처와 함께

블로그 이미지
by gregorio
  • Total hit
  • Today hit
  • Yesterday hit

'전체 글'에 해당되는 글 56건

  1. 2018.07.24
    Request Entity Too Large(HTTP 413, 414 code)
  2. 2018.07.19
    Spring Framework + Redis를 이용한 Session Clustering
  3. 2018.07.17
    Spring framework RequestMapping을 XML 파일로 생성하기
  4. 2018.07.17
    Crontab을 이용한 로그 백업
  5. 2018.07.11
    Chart.js를 이용한 Line Chart 그리기

Apache와 Tomcat AJP Protocol을 이용하여 연동하는 경우 Request Entity Too Large 오류가 발생할 때 다음과 같이 조치한다.


1. httpd.conf 파일 추가


LimitRequestLine 65536 

LimitRequestBody 0

LimitRequestFieldSize 65536 

LimitRequestFields 10000  


2. works.properties 파일 변경


worker.list=jkstatus,lb_dev


worker.template.type=ajp13

worker.template.lbfactor=1

worker.template.socket_timeout=300

worker.template.socket_connect_timeout=5000

worker.template.socket_keepalive=true

worker.template.connect_timeout=30000

worker.template.connection_pool_size=128

worker.template.connection_pool_minsize=32

worker.template.connection_pool_timeout=20


#########################################

## Worker

#########################################


worker.dev.reference=worker.template

worker.dev.host=10.255.116.84

worker.dev.port=8009

worker.dev.max_packet_size=65536 


#########################################

## Load Balancer

#########################################


worker.lb_dev.type=lb

worker.lb_dev.balance_workers=dev

worker.lb_dev.sticky_session=true


worker.jkstatus.type=status

#worker.jasper.max_packet_size=65536


 

Tomcat과 연동하는 workerdml max_packet_size를 65536으로 변경한다.


3. Tomcat의 server.xml 파일 수정


#JAVA_OPTS=" ${JAVA_OPTS} -Dorg.apache.coyote.ajp.MAX_PACKET_SIZE=65536"

 


MAX_PAKCET_SIZE를 JVM option에 추가하거나 


    <Connector URIEncoding="UTF-8" packetSize="65536" acceptCount="10" connectionTimeout="60000" enableLookups="false" maxPostSize="-1" maxThreads="256" tcpNoDelay="true" maxHttpHeaderSize="30000" port="8009" protocol="AJP/1.3" redirectPort="8443" />

 


server.xml 파일에 AJP Connector에 packetSize와 maxHttpHeaderSize를 설정한다.

AND

Tomcat 서버 다중화 시 Session Clustering을 통해 한 서버의 장애 시에도 지속적인 서비스가 가능하도록 구성한다.


Tomcat의 Session Clustering은 Multicast를 이용하여 서버간에 Session을 동기화 하는데

AWS와 같은 Cloud platform에서는 Muticast가 지원되지 않는다.


이 때 사용할 수 있는 방법이 Redis를 이용하여 Session 동기화가 필요한다.


Spring Framework에서는 Redis를 통해 Session Clustering을 지원하고 있다.


■ Dependency


spring-aop-4.3.1.RELEASE.jar

spring-beans-4.3.1.RELEASE.jar

spring-context-4.1.2.RELEASE.jar

spring-context-support-4.1.2.RELEASE.jar

spring-core-4.3.1.RELEASE.jar

spring-data-commons-1.12.10.RELEASE.jar

spring-data-keyvalue-1.1.10.RELEASE.jar

spring-data-redis-1.7.10.RELEASE.jar

spring-expression-4.3.1.RELEASE.jar

spring-jdbc-4.1.2.RELEASE.jar

spring-modules-validation-0.9.jar

spring-orm-4.1.2.RELEASE.jar

spring-security-config-4.1.2.RELEASE.jar

spring-security-core-4.1.2.RELEASE.jar

spring-security-web-4.1.2.RELEASE.jar

spring-session-1.3.1.RELEASE.jar

spring-session-data-redis-1.3.1.RELEASE.jar

spring-tx-4.1.2.RELEASE.jar

spring-web-4.1.2.RELEASE.jar

spring-webmvc-4.1.2.RELEASE.jar

spring-ws-core-2.4.0.RELEASE.jar

 



먼저 Session Clustering을 이용하기 위해 XML을 이용하여 설정한다.


 ■ context-redis.xml


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util"

xmlns:cache="http://www.springframework.org/schema/cache"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd

http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd

        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd

        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">


<!-- This should not be removed -->

<context:annotation-config/>

<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration"/>

<bean id="lettuceConnectionFactory" class="org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory" destroy-method="destroy">

<property name="hostName" value="#{project['session.redis.url']}" />

<property name="port" value="#{project['session.redis.port']}" />

</bean>

<!-- Do not remove -->

<util:constant static-field="org.springframework.session.data.redis.config.ConfigureRedisAction.NO_OP"/>

<!-- bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">

<property name="hostName" value="int-bus-mgmt-eh-dev.hez7xm.ng.0001.euw1.cache.amazonaws.com" />

<property name="port" value="6379" />

<property name="usePool" value="true" />

</bean-->

<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">

<property name="connectionFactory" ref="lettuceConnectionFactory" />

</bean> 

</beans>

 


<context:annotation-config>를 통해 Context내의 Annotation을 활성화 한다.


RedisHttpSessionConfiguration을 bean으로 설정한다.


LettuceConnectionFactory에 Redis hostname과 port를 정의한다.


AWS에서 제공하는 Redis를 사용할 경우에는 ConfigureREsisAction.NO_OP를 적용해야 오류가 발생하지 않는다.


설정이 완료되었으면 web.xml에 springSessionFilter를 추가해야 한다.


■ web.xml Filter 추가


   <filter>

    <filter-name>springSessionRepositoryFilter</filter-name>

    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

  </filter>

  <filter-mapping>

    <filter-name>springSessionRepositoryFilter</filter-name>

    <url-pattern>/*</url-pattern>

    <dispatcher>REQUEST</dispatcher>

    <dispatcher>ERROR</dispatcher>

  </filter-mapping>



이상으로 Spring Framework와 Redis의 연동은 완료되었다.


Redis에 Session이 저장되어 있는지 확인하기 위해 Redis Client를 설치하여 확인이 가능하다.


Redis client에 접속하기 위해 redis-cli -h "hostname" -p "port"로 접속이 가능하다.


keys * command를 이용하여 모든 키 정보 출력이 가능하다.

AND

Spring framework에서 Restful을 이용한 web service를 작성 시 어떤 서비스들이 Publishing 되어 있는지 확인이 필요한 경우가 있다.


Spring framework는 RequestMappingHandlerMapping을 통해서 Controller에 Request mapping 정보를 찾을 수 있다.


RequestMappingHandlerMapping을 이용하여 Mehtod Name, media type, parameter 등을 이용하여 XML로 생성하여 브라우저에 출력한다.


■ 소스코드


import java.io.ByteArrayOutputStream;

import java.io.OutputStream;

import java.lang.reflect.Method;

import java.util.HashMap;

import java.util.Iterator;

import java.util.LinkedHashMap;

import java.util.Map;

import java.util.Set;


import javax.annotation.Resource;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.xml.stream.XMLStreamWriter;


import org.springframework.http.MediaType;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.ResponseBody;

import org.springframework.web.method.HandlerMethod;

import org.springframework.web.servlet.mvc.condition.MediaTypeExpression;

import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;

import org.springframework.web.servlet.mvc.method.RequestMappingInfo;

import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;



@Controller

public class WadlCrt extends BaseController {

@Resource(name = "requestMappingHandler")

private RequestMappingHandlerMapping requestMappingHandler;

@RequestMapping(value="wadl", produces = MediaType.APPLICATION_JSON_VALUE)

public @ResponseBody Map<String, Object> generateWsdl(HttpServletRequest request, HttpServletResponse response) throws Exception {

Map<String, Object> resultMap = new HashMap<String, Object>();

Map<RequestMappingInfo, HandlerMethod> mappings = requestMappingHandler.getHandlerMethods();

Set<RequestMappingInfo> keys = mappings.keySet();

Iterator<RequestMappingInfo> iterator = keys.iterator();


ByteArrayOutputStream bos = new ByteArrayOutputStream();

XMLStreamWriter writer = XmlUtil.getWriter(bos);

XmlUtil.startDocument(writer, "UTF-8", "1.0");

String namespace = PropertiesUtil.getString("wadl.namespace");

// XmlUtil.startElement(writer, "application", PropertiesUtil.getString("wadl.namespace"));

XmlUtil.startElement(writer, "application");

XmlUtil.addNamespace(writer, "xmlns", "http://wadl.dev.java.net/2009/02");

XmlUtil.addNamespace(writer, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");

XmlUtil.addNamespace(writer, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema");

XmlUtil.addNamespace(writer, "xmlns:apigee", "http://api.apigee.com/wadl/2010/07/");

XmlUtil.addNamespace(writer, "xsi:schemaLocation", "http://wadl.dev.java.net/2009/02 http://apigee.com/schemas/wadl-schema.xsd http://api.apigee.com/wadl/2010/07/ http://apigee.com/schemas/apigee-wadl-extensions.xsd");

XmlUtil.startElement(writer, "resources");

XmlUtil.writeAttribute(writer,"base", "http://localhost:8080");

while(iterator.hasNext()) {

RequestMappingInfo key = iterator.next();

HandlerMethod value = mappings.get(key);


XmlUtil.startElement(writer, "resource");

PatternsRequestCondition condition = key.getPatternsCondition();

Set<String> patterns = condition.getPatterns();

if (patterns != null) {

for (String pattern : patterns) {

XmlUtil.writeAttribute(writer, "path", pattern);

}

}

Map<String, Object> list = new LinkedHashMap<String, Object>();

/** Get parameter type **/

Method method = value.getMethod();

XmlUtil.writeAttribute(writer, "id", method.getName());


/** Get request method **/

Iterator<RequestMethod> itr = key.getMethodsCondition().getMethods().iterator();

while(itr.hasNext()) {

RequestMethod mkey = itr.next();

XmlUtil.writeAttribute(writer, "name", mkey.toString());

list.put("method", mkey.toString());

}

Class<?>[] paramTypes = method.getParameterTypes();

int idx = 0;

if (paramTypes.length > 0) {

XmlUtil.startElement(writer, "request");

for (Class<?> paramType : paramTypes) {

XmlUtil.startElement(writer, "param");

String paramClass = paramType.getName();

list.put("input" + idx, paramClass);

XmlUtil.writeAttribute(writer, "type", paramClass);

XmlUtil.endElement(writer);

}

XmlUtil.endElement(writer);

}

/** Get return type **/

String returnType = method.getReturnType().getName();

if (returnType != null) {

XmlUtil.startElement(writer, "response");

list.put("output", returnType);

XmlUtil.startElement(writer, "param");

XmlUtil.writeAttribute(writer, "type", returnType);

/** Get media type **/

Iterator<MediaTypeExpression> itr1 = key.getProducesCondition().getExpressions().iterator();

String mediaType = null;

while(itr1.hasNext()) {

MediaTypeExpression exp = itr1.next();

mediaType = exp.getMediaType().toString();

list.put("mediaType", mediaType);

XmlUtil.writeAttribute(writer, "mediaType", mediaType);

}

/** param **/

XmlUtil.endElement(writer);

/** response **/

XmlUtil.endElement(writer);

}

/** Resource **/

XmlUtil.endElement(writer);

}


/** resources **/

XmlUtil.endElement(writer);

/** application **/

XmlUtil.endElement(writer);

XmlUtil.endDocument(writer);

LOGGER.debug("Created xml :: {}", bos.toString());

String xml = XmlUtil.prettyPrint(bos.toString());

OutputStream os = response.getOutputStream();

os.write(xml.getBytes());

LOGGER.info(xml);

return resultMap;

}


XML을 생성하기 위해 XMLStreamWriter를 이용하여 XML을 생성한다.


■ XML 생성 모듈


import java.io.ByteArrayOutputStream;

import java.io.IOException;

import java.io.StringReader;

import java.io.StringWriter;


import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.parsers.ParserConfigurationException;

import javax.xml.stream.XMLOutputFactory;

import javax.xml.stream.XMLStreamException;

import javax.xml.stream.XMLStreamWriter;

import javax.xml.transform.OutputKeys;

import javax.xml.transform.Transformer;

import javax.xml.transform.TransformerException;

import javax.xml.transform.TransformerFactory;

import javax.xml.transform.dom.DOMSource;

import javax.xml.transform.stream.StreamResult;


import org.w3c.dom.Document;

import org.xml.sax.InputSource;

import org.xml.sax.SAXException;


public class XmlUtil {



public static XMLStreamWriter getWriter(ByteArrayOutputStream bos) throws XMLStreamException {

XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(bos);

return writer;

}

public static void startDocument(XMLStreamWriter writer) throws XMLStreamException {

writer.writeStartDocument();

}

public static void startDocument(XMLStreamWriter writer, String encoding, String version) throws XMLStreamException {

writer.writeStartDocument();

}

public static void endDocument(XMLStreamWriter writer) throws XMLStreamException {

writer.writeEndDocument();

}

public static void addNamespace(XMLStreamWriter writer, String namespace, String  value) throws XMLStreamException {

writer.writeNamespace(namespace, value);

}

public static void startElement(XMLStreamWriter writer, String element) throws XMLStreamException {

writer.writeStartElement(element);

}

public static void startElement(XMLStreamWriter writer, String element, String value) throws XMLStreamException {

writer.writeStartElement(element, value);

}

public static void endElement(XMLStreamWriter writer) throws XMLStreamException {

writer.writeEndElement();

}

public static void writeAttribute(XMLStreamWriter writer, String name, String value) throws XMLStreamException {

writer.writeAttribute(name, value);

}

public static String prettyPrint(String xml) throws ParserConfigurationException, SAXException, TransformerException, IOException {

StringBuilder sb = new StringBuilder();

sb.append("<?xml version=\"1.0\" ?>");


if (xml == null) {

return sb.toString();

}

Document document = toXmlDocument(xml);

TransformerFactory transformerFactory = TransformerFactory.newInstance();

Transformer transformer = transformerFactory.newTransformer();

transformer.setOutputProperty(OutputKeys.INDENT, "yes");

transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");

transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

DOMSource source = new DOMSource(document);

StringWriter strWriter = new StringWriter();

StreamResult result = new StreamResult(strWriter);


transformer.transform(source, result);


sb.append("\n");

sb.append(strWriter.getBuffer().toString());

return sb.toString();


}


public static Document toXmlDocument(String str) throws ParserConfigurationException, SAXException, IOException {


DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();

DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();

Document document = docBuilder.parse(new InputSource(new StringReader(str)));


return document;

}


■ 출력 결과


<?xml version="1.0" ?>
<application xmlns="http://wadl.dev.java.net/2009/02" xmlns:xsi:schemaLocation="http://wadl.dev.java.net/2009/02 http://apigee.com/schemas/wadl-schema.xsd http://api.apigee.com/wadl/2010/07/ http://apigee.com/schemas/apigee-wadl-extensions.xsd" xmlns:xmlns:apigee="http://api.apigee.com/wadl/2010/07/" xmlns:xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <resources base="http://localhost:8080">
    <resource id="searchCodeList" path="/deploy/searchCodeList">
      <response>
        <param type="java.util.Map"/>
      </response>
    </resource>
    <resource id="mainPageView" path="/deploy/mainpage.do">
      <response>
        <param type="java.lang.String"/>
      </response>
    </resource>
    <resource id="selectDeployList" path="/deploy/selectDeployList">
      <request>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param type="java.util.Map"/>
      </response>
    </resource>
    <resource id="deploySource" path="/deploy/deployTarget">
      <request>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param type="java.util.Map"/>
      </response>
    </resource>
    <resource id="hudsonJobBuild" path="/deploy/jobBuild">
      <request>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param type="java.util.Map"/>
      </response>
    </resource>
    <resource id="sourceBuild" path="/deploy/uploadFile">
      <request>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param type="java.util.Map"/>
      </response>
    </resource>
    <resource id="showPage" name="GET" path="/system/showPerfPage.do">
      <response>
        <param mediaType="application/json" type="java.lang.String"/>
      </response>
    </resource>
    <resource id="processPerfData" name="POST" path="/system/perf">
      <request>
        <param type="javax.servlet.http.HttpServletRequest"/>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param mediaType="application/json" type="java.util.Map"/>
      </response>
    </resource>
    <resource id="chartPerf" name="POST" path="/system/chartPerf">
      <request>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param mediaType="application/json" type="java.util.Map"/>
      </response>
    </resource>
    <resource id="searchPerfCpu" name="POST" path="/system/searchPerfCpu">
      <request>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param mediaType="application/json" type="java.util.Map"/>
      </response>
    </resource>
    <resource id="searchPerfMem" name="POST" path="/system/searchPerfMem">
      <request>
        <param type="java.util.Map"/>
      </request>
      <response>
        <param mediaType="application/json" type="java.util.Map"/>
      </response>
    </resource>
    <resource id="generateWsdl" path="/wadl">
      <request>
        <param type="javax.servlet.http.HttpServletRequest"/>
        <param type="javax.servlet.http.HttpServletResponse"/>
      </request>
      <response>
        <param mediaType="application/json" type="java.util.Map"/>
      </response>
    </resource>
  </resources> 

</application> 




AND

Apache에서 rotatelogs를 이용하여 로그 파일을 매일 Rotation할 수 있다.

그러나 로그 파일이 날찌별 동일 디렉토리에 생성되기 때문에  월별로 디렉토리를 생성하여 로그 백업이 필요한 경우 사용할 수 있다.


먼저 shell을 작성한다.


■logback.s


#!/bin/sh


CUR_DATE=`date +"%Y%m%d"`

LOG_FILE=log_cron_$CUR_DATE.log

LOG_HOME=/logs001/apache

LOG_BACK_DIR=$LOG_HOME/`date +"%Y%m"`

ACCESS_LOG_FILE=$LOG_HOME/access_$CUR_DATE.log

ERROR_LOG_FILE=$LOG_HOME/error_$CUR_DATE.log

HEALTH_LOG_FILE=$LOG_HOME/health_$CUR_DATE.log


#Make log backup directory

if [ ! -d $LOG_BACK_DIR ]; then

    echo "$LOG_BACK_DIR is created" >> $LOG_FILE

    mkdir $LOG_BACK_DIR

fi


#Move log file to backup directory

for entry in $LOG_HOME/*

    do

        if [ ! -d $entry ]; then

            if [ $entry != $ACCESS_LOG_FILE ] &&

               [ $entry != $ERROR_LOG_FILE ] &&

               [ $entry != $HEALTH_LOG_FILE ]; then

                echo "$entry is moved to $LOG_BACK_DIR" >> $LOG_FILE

                #mv $entry $LOG_BACK_DIR

            fi

        fi

    done 


Shell  작성이 완료되었으면 Crontab에 등록하여 주기적으로 shell을 실행하도록 한다.


crontab -e 명령을 이용하여 cron에 Job을 등록한다.



10 0 * * * /engn001/script/logback.sh 


매일 0시 10분에 logback.sh가 실행된다.


Crontab 등록 완료되었으면 crontab -l을 이용하여 등록된 내용을 확인한다.



AND

시스템 개발 시 시각적인 정보를 제공하기 위해 Chart를 이용하는 Line, Bar 등 다양한 Chart를 화면에 보여주게 된다.


Chart.js를 사용하기 위해서는 먼저 JSP에 <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js">를 정의한다.


Chart.js는 Chart 그리는 영역을 정의해 주어야 한다.


■Chart 영역 정의


<div>

    <canvas id="cpu_line_chart" width="800" height="200"></canvas>

</div> 


Chart의 넓이와 높이를 정의한다.



서버에서 받아온 데이터를 이용하여 Chart를 그리는 예제이다.

■Chart 그리기


function fn_searchCallBack(result){


var cpuLabels = [];

var cpuUsed = [];

var cpuFree = [];

var sysCpu = [];

var sysUser = [];

$.each(result.cpuInfo, function(i, val){

var dispTime = gfn_nvl(val.updDate);

console.log(dispTime);

dispTime = dispTime.substring(8,14);

console.log(dispTime);

cpuLabels.push(dispTime);

cpuUsed.push(gfn_nvl(val.cpuUsed));

cpuFree.push(gfn_nvl(val.cpuFree));

sysCpu.push(gfn_nvl(val.sysCpu));

sysUser.push(gfn_nvl(val.sysUser));

});

new Chart(document.getElementById("cpu_line_chart"), {

  type: 'line',

  data: {

    labels: cpuLabels,

    datasets: [{ 

        data: cpuUsed,

        label: "Used(%)",

        borderColor: "#3e95cd",

        fill: false

      }, { 

        data: cpuFree,

        label: "Free(%)",

        borderColor: "#8e5ea2",

        fill: false

      }, { 

        data: sysCpu,

        label: "Sys(%)",

        borderColor: "#3cba9f",

        fill: false

      }, { 

        data: sysUser,

        label: "User(%)",

        borderColor: "#e8c3b9",

        fill: false

      }

    ]

  },

  options: {

    title: {

      display: true,

      text: 'CPU Usage'

    }

  }

});

}


먼저 Chart에 Mapping이 필요한 변수를 정의한다.

Line Char는 Array를 사용하기 때문에 var cpuLabels = []; 처럼 Array 변수를 선언한다.


Loop를 돌면서 서버에서 받은 응답 데이터를 Array 변수에 저장한다.


new Chart를 이용하여 Chart를 생성하는데, Chart의 영영이 파라미터로 전달된다.


또한 Array 변수들은 data로 매핑하면 Line Chart가 그려지게 된다.




AND

ARTICLE CATEGORY

분류 전체보기 (56)
Spring Framrwork (33)
Linux (1)
APM (1)
Java (8)
python (0)
ant (1)
chart (1)
OS (1)
tomcat (1)
apache (1)
database (0)

RECENT ARTICLE

RECENT COMMENT

CALENDAR

«   2024/10   »
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

ARCHIVE

LINK