우분투(ubuntu) 서버 설정

우분투 기본 환경 설정

1. Ubuntu 7.1 Server 설치

# sudo apt-get update
# sudo apt-get dist-update


2. Locale 변경
# sudo locale-gen ko_KR.EUC-KR
# /etc/environment 의 LANG를 ko_KR.EUC-KR로 수정


3. Webmin 설치

# Webmin 다운로드 : webmin_1.400_all.deb
# dpkg --install webmin_1.400_all.deb
# apt-get install perl libnet-ssleay-perl openssl libauthen-pam-perl libpam-runtime libio-pty-perl libmd5-perl


4. apache2 환경설정

# sudo a2enmod rewrite : mod_rewrite추가
# /etc/apache2/sites-available/default 에 AllowOverride all로 변경
# /etc/apache2/Apache2.conf에 다음의 설정을 추가/확인
<IfModule mod_rewrite.c>
RewriteEngine On
</IfModule>


5. proFTPD 환경설정

# sudo apt-get install proftpd
# /etc/proftpd/proftpd.conf에 다음의 내용을 추가
<Global>
RootLogin off # root 로그인 방지
AllowForeignAddress on # 외부 접속 허용/금지
AllowRetrieveRestart on # 이어받기 허용/금지
AllowStoreRestart on # 이어쓰기 허용/금지
DeferWelcome on
ServerIdent on
LoginPasswordPrompt on # 사용자 암호 묻기
AllowOverwrite on # 겹쳐쓰기 허용
AuthAliasOnly on # 인증된 alias 사용자만 사용
UserAlias #### #### (ftp user, shell user : 필요한 사용자 이름으로 지정)
UseFtpUsers on # Ftp용 사용자를 사용
RequireValidShell off # shell로 로그인된 사용자만 사용
HiddenStor on # hidden속성 저장
</Global>

6. Samba 환경설정


# sudo smbpasswd -a <userid> : Samba 사용자 추가
# /etc/samba/smb.conf Global 옵션 추가/확인
[global]
encrypt passwords = true
netbios name = <server name>
server string = <server name>
writable = yes
invalid users = root
workgroup = WORKGROUP
unix charset = euc-kr
# /etc/samba/smb.conf 에 공유디렉토리 추가
[test]
hide dot files = no
delete readonly = yes
path = /../../.. (공유할 디렉토리)
sync always = yes
# sudo /etc/init.d/samba restart


7. Java 설치

# sudo apt-get install sun-java5-bin sun-java5-plugin
# sudo update-alternatives --config java : Java Defualt를 설정

8. Tomcat & JDK 설치

# sudo apt-get install tomcat5.5 sun-java6-jdk tomcat5.5-webapps tomcat5.5-admin
# /etc/default/tomcat5.5 에 JAVA_HOME 설정/확인
JAVA_HOME=/usr/lib/jvm/java-6-sun
# sudo update-alternatives --config java : Java Defualt를 설정
# sudo /etc/init.d/tomcat5.5 start : 톰켓 시작
# wget http://localhost:8180 : 접속하여 확인

9. SVN 설치

# sudo apt-get install subversion libapache2-svn
# svnadmin create --fs-type fsfs /../../.. : 리포지토리 디렉토리 생성
# svnserve -d -r /../../.. : 시작 스크립트
# killall svnserve : 종료 스크립트

10. build환경 구성
# sudo apt-get install gcc
# sudo apt-get install build-essential

특이사항
# 보안상 문제가 있을지라도 Webmin은 상당히 좋은 툴 이다. 이런저런 스크립트가 내장되어 대부분 gui에서 실행하면 스크립트와 설정파일을 얻을수가 있다

# 문자세트를 euc-kr로 통일 Ftp, Web, Samba, Local에서 만든 한글 디렉토리또는 파일이름이 잘 출력된다.

Object-Relational Mapping Strategies

Table of Contents

시작하기전 기초지식 살펴보기

ORM을 이용해 더 좋은 작업을 하기위해서는 object modeling과 relational modeling, 두 modeling의 유사점과 차이점을 이해해야만 합니다.

Object Modeling

object modeling은 OOP를 기반으로 생성된 시스템을 표현합니다.
이러한 object-modeling에는 identity, state, behavior, encapsulation등의 많은 개념이 포함되어있습니다.

    • 기본 개념
      • Identity
      • State
      • Behavior
      • Encapsulation
    • 높은 레벨의 개념
      • Type
      • Associations
      • Class
      • Inheritance

Relational Modeling

relatipnal modeling은 서술어와 비슷한 truth statemet로 정보를 표현합니다.
개념들을 소개하면 다음과 같습니다.

    • 기본개념
      • Relation
      • Attribute
      • Domain
      • Tuple
      • Attribute Value
      • Relation Value
      • Relation Variable
      • Daatabase
      • Base Relation Values
      • Derived Relation Values
      • Coupling between relations, variables, and values
몇 가지 개념 설명
  • Tuple
    <Person SSN#="123-45-6789" Name="ryu sung hee" City="seoul">

    이중에서 SSN#="123-45-6789", Name="ryu sung hee", City="seoul" 각각이 하나의 tuple입니다.
    tuple의 정렬은 아무 의미가 없습니다.

  • Attribute Value
    attribute value는 각각의 tuple에 포함된 attribute의 value입니다.
    • Database와 Relational modeling의 기본적인 개념비교
      Common DatabaseRelational
      tablerelation valiable
      rowtuple
      columnattribute
      column valueattribute value
      databasedatabase

보다 자세한 설명은 http://www.chimu.com/publications/objectRelational/ 웹문서를 참조하십시오.

Object-Relational Mapping이란?

Object-Relational Mapping(이하 ORM)은 object와 relational modeling 구현물 사이, 그리고 이 구현물을 서포트하는 시스템간의 변환 프로세스입니다. 조금 더 쉽게 설명하자면 객체와 테이블, 시스템(RDBMSs)을 변형 및 연결해주는 작업이라 말 할 수 있습니다. ORM을 이용한 개발은 객체와 데이터베이스의 변형에 유연하게 대처할 수 있도록 해줍니다.

  • Transparent Persistence

    ORM에서, object programming language를 사용한 relational database에 저장된 데이터를 직접적으로 조작하는 능력을 Transparent persistence라고 합니다. Transparent persistent는 ODBC 또는 JDBC등을 사용하는 database sub-language와는 다릅니다.

Why ORM?

  • (과장되게 표현하여)ORM을 이용하면 CRUD를 위해 긴 SQL문장을 작성할 필요가 없습니다. 쿼리 작성은 여전히 필요하지만, ORM 툴(HQL 등)을 이용하면 한충 쉽게 만들 수 있습니다. 또 JDBC와 관련된 복잡한 코드 작업으로부터 해방될 수 있습니다.
  • ORM을 이용하면 관계형 모델과 관련된 성능 오버헤드를 수반하지 않고도 요구사항에 적합한 도메인 모델을 생성할 수 있으며, 로우와 컬럼이 아닌 오브젝트의 관점에서 작업을 수행하는 것이 가능합니다.
  • ORM은 변경 사항을 자동으로 감지하므로, 전체 개발 라이프사이클에 걸쳐 에러의 가능성을 줄일 수 있습니다.
  • ORM은 데이타베이스 벤더 별로 제공되는 SQL 구문에 대한 종속성을 줄이고 호환성을 향상시켜 줍니다. SQL 구문은 ORM 툴에 의해 추상화가 가능합니다.

ORM사용 선택을 위한 몇 가지 가이드라인

적절하게 활용되는 경우, ORM은 개발 작업에 투자되는 인력을 절감하고 관리 능력을 개선하는 효과를 제공합니다. JDBC 대신 ORM 솔루션을 적용함으로써 작성되어야 하는 Java 코드의 양을 30~50% 절감하는 경우도 드물지 않습니다. 이렇게 절감된 인력을 비즈니스 기능의 구현에 투자할 수 있다는 점에서, ORM 솔루션은 분명한 효과가 있습니다. 적절한 환경, 적절한 애플리케이션에 ORM을 적용함으로써 얻을 수 있는 잠재적인 효과는 무조건적으로 무시하거나 외면하는것은 합리적인 태도가 아닙니다.

ORM 툴에 대해 부정적인 시각을 가진 이들은, 적절하지 않은 환경에 ORM 툴을 적용한 경우가 많습니다. 맞지 않는 환경에 ORM을 억지로 적용시키는 경우만 아니라면, ORM은 훌륭한 결과를 보여줍니다. 몇 가지 기본적인 가이드라인이 아래와 같습니다:

  • 타겟 데이타베이스를 이해하라. ORM을 적용할 때 SQL 및 데이타베이스의 락킹(locking) 모델을 무시해도 된다고 생각하면 오산입니다. O-R 매핑은 작업을 쉽게 해 주는 툴이지, 구현되는 환경에 대한 이해를 불필요하게 만드는 툴은 아닙니다. (ORM 적용 환경에서 발생하는 많은 문제가 데이타베이스와 SQL의 문제를 간과함으로써 발생합니다.)
  • 필요한 경우 SQL을 사용하는 것을 두려워하지 말라. 많은 시나리오에서 이 방법으로 효과를 볼 수 있습니다. Hibernate, TopLink와 같은 ORM 제품은 SQL 쿼리 작성 기능을 제공합니다. 하지만 경우에 SQL문을 직접 작성해야 할 경우도 있습니다.
  • O-R 매핑 제품을 선택하기 전에 충분히 검토하라. 모든 ORM 제품이 동일한 수준의 기능을 제공하는 것은 아닙니다. 요구사항을 반영하는 환경을 구축하고 2~3 가지 제품을 비교 테스트해 보아야 합니다. 이 과정을 통해 ORM이 성능 기준을 만족하는지 검증할 수 있습니다. 엔터프라이즈 개발 과정의 다른 요소들과 마찬가지로, 프로젝트 라이프사이클의 초기 단계에서 성능과 관련한 리스크를 최소화하는 것이 중요합니다. 또 ORM 툴의 매핑 기능에 과도한 오버헤드가 수반되지 않는지 확인해야 합니다.
  • ORM이 적절하게 사용될 수 있는 상황을 이해하라. ORM은 엔티티를 개별적으로 업데이트하고 간헐적으로 셋 기반 작업을 수행하는 OLTP 애플리케이션에 특히 적합합니다. 고객 레코드 및 주문 내역을 개별적으로 업데이트하는 애플리케이션이 좋은 예입니다.
  • ORM이 적절하지 않은 경우를 이해하라. ORM은 만병통치약이 아닙니다. ORM이 적합하지 않은 경우가 아래와 같습니다:
    • 많은 수의 레코드에 대해 잦은 빈도로 벌크 업데이트를 수행하는 애플리케이션
    • OLAP 애플리케이션
      데이터마이닝을 위해 사용되는 어플리케이션에서는 이미 데이터를 본래의 엔티티 상태로 사용하기 어렵습니다.
      OLAP에 대해서는 http://home.pusan.ac.kr/~pnustat/info/DataMining/3-2.htm를 참조하십시오.
    • 데이타의 인출 및 업데이트를 위해 핸드코딩으로 작성된 SQL 및 저장 프로시저를 이용하는 데이타베이스 환경.
      이 경우 JDBC 기반의 접근 방법이 최선의 선택이 될 수 있습니다. iBATIS SQL Maps 역시 이러한 환경에서 빛을 발합니다. (하지만 몇몇 ORM 제품은 기존 스키마 및 저장 프로시저와 효과적으로 연동될 수 있으므로, 이러한 환경에서도 ORM의 사용을 고려할 가치가 있습니다.)
    • 순수 SQL 기반 접근 방법을 적용하는 것이 적절한 애플리케이션.
      비 즈니스 로직의 대부분이 데이타베이스에 이미 구현되어 있거나, 데이타베이스 무결성 제약(integrity constraint)이 적용되어 있는 경우 등을 그 예로 들 수 있습니다. 이러한 애플리케이션에서는 오브젝트 또는 ORM의 활용 여지가 매우 적으며, 데이타베이스 테이블을 도메인 오브젝트로 모델링 함으로써 기대할 수 있는 효과가 거의 없습니다.

Mapping Basic

자바 클래스는 RDBMS에 매핑될 수 있으며, 우리는 여기에서 persistent class와 table 사이의 mapping에 대해 알아볼 것입니다.

  1. one-to-one

    persistent class와 테이블 사이의 가장 심플한 매핑은 one-to-one 방식입니다. 이 방식에서 모든 persistent class의 attribute들은 테이블의 모든 컬럼들에 의해 다시 표현됩니다. ( attribute:column = 1:1 ) 이러한 매칭 방식은, business class의 각 instence을 매핑되는 테이블의 하나의 row에 저장시킵니다.

  2. class-to-table

    비록 매핑 타입이 적절하더라도, 존재하는 object와 entity-relation model 사이에서는 충돌이 발생하곤 합니다. 이러한 실세계와 object modeld의 차이를 극복하기위해 calss-to-table mapping은 다음의 두가지 방법으로 이를 지원합니다.

    • SUBSET Mapping
      persistent class의 attribute들은 테이블의 특정 컬럼으로 표현되거나, persistent class와 매핑되는 테이블의 모든 컬럼으로 표현됩니다. subset mapping은 상속관계의 class를 매핑할 때에도 사용됩니다. ("상속 트리에서의 class 매핑"에서 좀더 자세히 설명하도록 하겠습니다.)
    • SUPERSET Mapping
      superset mappping된 persistent class는 multiple tables의 컬럼으로부터 파생된 attributes를 포함합니다. 이러한 매핑 타입은 table spanning으로도 잘 알려져있으며 view, join, inheritence tree 등을 표현하기위해 사용됩니다.

상속 트리에서의 class 매핑

RDBMS에서의 상속 트리를 클래스로 표현하기위해 사용되는 가장 일반적인 전략이 "vertical mapping, horizontal mapping, filtered mapping"입니다. (이 세가지 전략이 inheritence tree를 위해서만 이용되는것은 아닙니다.)

handy tip
  • abstract and concrete class
    abstract class는 부모 클래스로서 디자인된 클래스를 의미합니다. 반면에 concrete class는 entity로서 생성되어진 클래스입니다. concrete class는 abstract class와는 다릅니다. 아래의 예제에서 "Student", "Professor"가 concrete class가 됩니다.
    보다 자세한 정보는 http://encyclopedia.thefreedictionary.com/concrete+class 웹페이지의 "Abstract and concrete classes"를 참조하십시오.

'

  • 각각의 매핑 타입을 이해하기위해 다음의 UML 클래스 다이어그램을 이용합니다.
    '
  1. vertical mapping (One table per subclass)

    vertical mapping 방식은 각 클래스마다 테이블을 생성합니다. 모든 branch와 leaf 테이블들은 부모 테이블과 연결되며, 부모 테이블의 primary key를 참조합니다. vertical mapping의 장점은 object-oriendted concept에 가장 순응한다는 점입니다. 또한 테이블의 수정이나, 추가만 필요하므로 superclass와 subclass를 수정하기가 매우 쉽습니다. 그러나 복잡한 결과물이나 상속관계에서는 사용하기가 어렵습니다.
    '

  2. horizontal mapping (One table per concrete class)

    horizontal mapping 방식은 각 concrete class마다 테이블을 생성합니다. concrete class에는 자기 자신의 모든 attribute들이 컬럼으로 포함되어있으며, 추상화 상위 class의 모든 attribute들 또한 포함되어있습니다. 이러한 mapping 방법의 구현물들은 매우 빠른 성능을 제공하며, 디자인하기에도 간단합니다. 그렇지만, 추상화 상위 class의 컬럼이 변경되면 연관된 많은 테이블들이 변경되어야 합니다. 따라서, 이 매핑 방법은 attribute에의해 좌우되는 상속 트리보다, 메소드에의해 좌우되는 상속트리에 가장 유용하게 이용할 수 있습니다.
    '

  3. filtered mapping (One Table per class herarchy)

    filtered mapping 방식에서는 모든 concrete class들이 같은 테이블로 매핑됩니다. 테이블은 상속 트리에서 추상화된 모든 attribute들과 concrete class들(mapping에 관련된 상속 트리의 class들)을 포함합니다. 또한, 테이블에 filter column이 생성됩니다. filter column은 subclass들을 분리할때 사용됩니다. filtered mapping 방식의 구현물들은 적절한 성능을 제공하지만, table-normalization rule에는 위배됩니다. 또한 클래스간의 coupling을 증가 시킵니다.
    '

    여기에서 objectType column은 filter column으로 이용됩니다.

vertical, horizontal, filtered mapping 전략 비교

세가지 mapping 전략을 비교하기위해 "상속 트리에서의 class 매핑"에서 사용된 UML에 간단한 TenuredProfessor class를 추가하도록 하겠습니다.

  • TernuredProfessor class를 추가한 UML
    '

    '
  1. vertical mapping (One table per subclass)
    '
    vertical mapping 전략은 하나의 class를 하나의 data entity로 매핑합니다. 때문에 한개의 TenuredProfessor table을 생성하면 됩니다. 하지만 상위 추상 레벨의 테이블 정보를 확인해야 하는 등 여러번에 걸친 데이터 엑세스를 필요로 하는 단점이 있습니다.
    '

    '
  2. horizontal mapping (One table per concrete class)
    '
    horizontal mapping 전략은 각각의 concrete class를 각각의 data entity로 매핑합니다. 이때에도 한개의 테이블만 생성하면 됩니다. 하지만 릴레이션이 변경되었을 때, 이 object를 어떻게 핸들링할것인가에 대한 문제가 남아있습니다. Professor 테이블의 정보를 TenuredProfessor에 더했기 때문에 이러한 릴레이션 문제는 더욱 복잡해 집니다.
    '

    '
  3. filtered mapping (One Table per class herarchy)
    '
    filtered mapping 전략은 상속관계의 class들을 하나의 data entity로 매핑합니다. 때문에 데이터의 update를 어떻게 해야 할지 고민해야 하며, 낭비되는 공간은 상당히 거슬리는 문제일 것입니다.
    '
  • maping 전략들은 각각의 장단점을 가지고 있습니다. 다음은 이를 간략히 비교한 표 입니다.
    Factors to Consider(비교요소)vertical mappinghorizontal mappingfiltered mapping
    Ad hoc reportingMedium/DifficultMediumSimple
    Ease of implementation(사용성)DifficultMediumSimple
    Ease of data accessMedium/SimpleSimpleSimple
    CouplingLowHighVery high
    Speed of data accessMedium/FastFastFast
    Support for polymorphism(다형성)HighLowMedium

Mapping Object Relationships

object의 관계를 매핑하는 방법으로는 아래와 같은 방법들이 있습니다.

  1. One-to-One Relationships = 1:1
  2. One-to-Many Relationships = 1:n
  3. Owned Relationships (Aggregation)
  4. Referenced Relationships (Association)
  5. Many-to-Many Relationships = n:n

Join Table에 대한 모델링

Join Table에 대한 모델링 방법으로는 아래와 같은 방법들이 있습니다.

  1. Transactional Joins
  2. Transparent Joins
  3. Self-Join Tables

Mapping Examples

  • Class Model
    '

    '
  • Data Model 결과물
    '

    '
  • class, data model 매핑 분석
    1. vertical mapping (One table per subclass)
      • class = Person, Corporation, Individual
      • table = TB_PERSON, TB_CORPORATION, TB_INDIVIDUAL
      • source for hibernate
        Person.hbm.xml
        <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"	"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"><hibernate-mapping>  <class name="eg.hibernate.mapping.dataobject.Person" table="TB_PERSON" polymorphism="implicit">    <id name="id" column="ID">      <generator class="assigned"/>    </id>    <set name="rights" lazy="false">      <key column="REF_PERSON_ID"/>      <one-to-many class="eg.hibernate.mapping.dataobject.Right" />    </set>    <joined-subclass name="eg.hibernate.mapping.dataobject.Individual" table="TB_INDIVIDUAL">      <key column="id"/>      <property name="firstName" column="FIRST_NAME" type="java.lang.String" />      <property name="lastName" column="LAST_NAME" type="java.lang.String" />    </joined-subclass>    <joined-subclass name="eg.hibernate.mapping.dataobject.Corporation" table="TB_CORPORATION">      <key column="id"/>      <property name="name" column="NAME" type="string" />      <property name="registrationNumber" column="REGISTRATION_NUMBER" type="string" />    </joined-subclass>  </class></hibernate-mapping>
    2. horizontal mapping (One table per concrete class)
      • class = Estate, Building, Land
      • table = TB_BUILDING, TB_LAND
      • source for hibernate
        Estate.hbm.xml
        <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC   "-//Hibernate/Hibernate Mapping DTD 2.0//EN"  "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"><hibernate-mapping>  <class name="eg.hibernate.mapping.dataobject.Land" table="TB_LAND" polymorphism="implicit">    <id name="id" column="ID">      <generator class="assigned"/>    </id>    <property name="description" column="DESCRIPTION" type="java.lang.String" />    <property name="squareFeet" column="SQUARE_FEET" type="java.lang.Double"/>  </class>  <class name="eg.hibernate.mapping.dataobject.Building" table="TB_BUILDING" polymorphism="implicit">    <id name="id" column="ID">      <generator class="assigned"/>    </id>    <property name="description" column="DESCRIPTION" type="java.lang.String" />    <property name="address" column="ADDRESS" type="java.lang.String"/>  </class></hibernate-mapping>
    3. filtered mapping (One Table per class herarchy)
      • class = Lease, property, Right
      • table = TB_RIGHT
      • source for hibernate
        Right.hbm.xml
        <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE hibernate-mapping PUBLIC   "-//Hibernate/Hibernate Mapping DTD 2.0//EN"  "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"><hibernate-mapping>  <class name="eg.hibernate.mapping.dataobject.Right" table="TB_RIGHT" polymorphism="implicit">    <id name="id" column="ID">      <generator class="assigned"/>    </id>    <discriminator>      <column name="DISCRIMINATOR"/>    </discriminator>    <property name="date" column="DATE" type="java.sql.Date" />    <many-to-one name="person" class="eg.hibernate.mapping.dataobject.Person" column="REF_PERSON_ID"/>    <any name="estate"         meta-type="string"         id-type="java.lang.Integer">      <meta-value value="LND" class="eg.hibernate.mapping.dataobject.Land"/>      <meta-value value="BLD" class="eg.hibernate.mapping.dataobject.Building"/>      <column name="REF_ESTATE_TYPE"/>      <column name="REF_ESTATE_ID"/>              </any>        <subclass name="eg.hibernate.mapping.dataobject.Property" discriminator-value="PRO"/>        <subclass name="eg.hibernate.mapping.dataobject.Lease" discriminator-value="LEA">      <property name="duration" column="DURATION" type="java.lang.Integer" />     </subclass>  </class></hibernate-mapping>

참고문헌

FrameWork

소 프트웨어의 세계에서, 어플리케이션 소프트를 개발할 때에 빈번히 쓰여지는 범용 기능을 한꺼번에 제공하여, 어플리케이션의 토대로서 기능하는 소프트웨어이다. 어플리케이션의 아웃라인. 개발에 프레임워크를 이용하면 독자적으로 필요로 하는 부분만을 개발하면 되기 때문에 개발 효율의 향상을 기대할 수 있다. 구체적인 소프트웨어 뿐만 아니라, 범용으로 적용 가능한 프로그램의 설계 모델이나 전형적인 처리 패턴 등도 포함한 의미로 프레임워크라고 부르는 경우도 있다

GoF의 디자인 패턴으로 유명한 랄프 존슨(Ralph Johnson) 교수는 프레임워크를 "소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔 일련의 협업화된 형태로 클래스들을 제공하는 것"이라고 정의하였다. 프레임워크는 라이브러리와 달리 애플리케이션의 틀과 구조를 결정할 뿐 아니라, 그 위에 개발된 개발자의 코드를 제어한다. 프레임워크는 구체적이며 확장 가능한 기반 코드를 가지고 있으며, 설계자가 의도하는 여러 디자인 패턴의 집합으로 구성되어 있다.

문자 그대로 해석하면프로그램의 골격

- Struts

Jakarta프로젝트가 개발하고 있는 오픈소스의 프레임워크

- Spring

J2EE기반의 웹 어플리케이션을 쉽게 하는 것을 목적으로 하고 있으면서 J2EE서비스에 종속되지 않으며, 계층화, 모듈화가 잘 되어 있는 유연한 프레임워크 이다.

스프링의 주요목적중 하나인 "기존의 잘 만들어진 솔루션들과 경쟁하지 않고, 이들과의 통합을 추진한다."는 점이 다른 프레임워크들과 유기적으로 잘 연동할 수 있을 것인가를 알 수 있게 한다.

스 프링 프레임워크는 다른 프로젝트에서 개발된 컴포넌트를 어떻게 잘 조합하여 사용할 것인가에 대한 고민의 해답으로 제시된 IoC(제어역행화; Inversion of Control)* 또는 연관성 삽입(Dependency Injection)* 패턴이 스프링의 중심된 개념이다.

- Hibernate
ORM (Object Relational Mapping)* 프레임워크

- ibatis

ORM (Object Relational Mapping)* 프레임워크

- Pico

Ioc (Inversion of Control)*컨테이너의 일종

- Gauce

우리나라꺼인듯

- Tiles

템플릿 엔진 (레이아웃 구성을 편하게 해주는 거인듯)

- Velocity

자카르타 프로젝트에서 진행 중인 자바 기반의 템플릿 엔진.

템플릿으로부터 SQL, HTML, 자바 소스를 생성하는 등 사용할수 있는 용도가 다양하지만, JSP의 대안으로 웹 어플리케이션 개발에서 많이 사용된다.

스트럿츠는 JSP를 통해 구현된 뷰를 프리젠테이션의 기본으로 하고 있지만, 일부 개발자들은 벨로시티를 선호하고 있다.

WAS (Web Application Server)

WAS 는 웹이 탄생한 이래, 주로 데이터베이스 조회나 일반적인 비즈니스 로직에 대한 처리를 위해 다양한 언어로 개발된 인터넷/인트라넷 환경의 소프트웨어를 지칭한다. 자바스크립트나 JSP 등과 같은 스크립트 및 서비스들은 대개 최신의 데이터를 검색하기 위해 데이터베이스에 접근하고, 브라우저 또는 클라이언트 프로그램을 통해 사용자들에게 검색 결과를 제공한다.

WAS를 비롯한 애플리케이션 서버들은, 웹서버* 즉 HTTP 서버와 같은 컴퓨터를 공유할 수도 있지만, 별개의 컴퓨터를 독립적으로 사용하는 경우도 많다. 대규모 사이트에서는, 오히려 WAS와 웹서버 등을 위해 여러 대의 컴퓨터가 동원되기도 한다. 넷스케이프의 Netscape Application Server, BEA의 Weblogic Enterprise, 볼랜드의 AppServer, 그리고 IBM의 Websphere Application Server 등이 WAS의 대표적인 제품들이다.

- Web-logic (BEA)

BEA WebLogic은 100% 자바로 구현되었고 EJB(Enterprise Java Beans) 표준을 처음으로 구현한 제품이다. BEA

Weblogic4.0에서는 미션크리티컬한 업무수행을 위해 Clustering, Failover, Load Balancing 기능 등이 보강되었다.
- 제우스 (티맥스 소프트)

국산이다

- Web-sphere (IBM)

IBM 꺼 -_-

UML Tools

툴에 대해서 자세히 알 필요는 없을듯.. UML에 대해서만 알아봅시다! 사실 찾기가 힘들었음

- Together

- Rational Rose

- Omondo (eclipse-plugin)

UML (Unified Modeling Language)
UML 이란 소프트웨어 개발 과정에서 산출되는 산출물들을 명시, 개발, 문서화하기 위한 모델링 언어이다. UML은 Rational 사의 Grady Booch, James Rumbaugh에 의해 1994년 10월에 처음 개발에 착수되었다. 이후 1995년 10월에 Unified Method 0.8의 명칭으로 OOPSLA '95에서 발표되었으며, 이후 Ivar Jacobson이 UML 개발에 함께 협력하면서 1996년에 버전 0.9를 발표하였고, 1997년 11월에는 UML 1.1 이 OMG에 의해 표준으로 채택되었다.
UML은 모델링 언어일뿐 메쏘드(또는 방법론)는 아니다. 메쏘드는 프로세스에 대한 정의와 각각의 업무들에 대한 지침과, 업무들 간의 순서들을 명시해야 하는 반면, 모델링 언어는 표기법(또는 다이어그램)들만을 제시하는 것이다. 따라서 UML은 소프트웨어 개발에 사용하기 위한 여러 다이어그램들을 정의하고 있으며, 또 다이어그램들의 의미들에 대해 정의하고 있다.
UML 은 여러가지 다이어그램들을 제시함으로써 소프트웨어 개발과정의 산출물들을 비주얼하게 제공하고, 개발자들과 고객 또는 개발자들 간의 의사소통을 원활하게 할 수 있도록 하고 있다. UML은 시스템을 모델링 할 수 있는 다양한 도구들을 제공하기 때문에, 도메인을 모델링하기가 훨씬 용이할 뿐만 아니라 모델링한 결과를 쉽게 파악할 수 있게 된다. 또한 산업계 표준으로 채택되었기 때문에 UML을 적용한 시스템은 신뢰성 있는 시스템으로 평가받을 수 있다.

DATABASE

여 러 개의 어플리케이션 소프트 또는 유저에 의해 공유 되는 데이터 집합. 또는, 그런 관리 시스템을 포함해서 데이터 베이스라고 하는 경우도 있다. 데이터베이스라는 말은 1950년대에 미군에 의해 쓰여지기 시작했다. 데이터의 집합을 겉 모양으로 표현한 레이셔널 데이터베이스가 주류였으나, 최근에는 데이터의 집합을 절차와 데이터를 일체화 한 오브젝트의 집합으로서 다루는 오브젝트 데이터베이스가 대규모 시스템 등에서 이용되고 있다. 데이터 베이스의 조작이나 보수, 관리를 위한 소프트웨어를 DBMS라

한다

- Oracle

Oracle 사의 대표적인 제품으로써 전 세계적으로 가장 많은 수요자 층을 확보하고 있는 제품이다.

유닉스, 리눅스, 윈도우 버전이 따로 있으며 리눅스 버전의 경우에는 홈페이지에서 다운로드 받아 개발용으로 사용할 수 있다.

- MS SQL Server

마이크로소프트의 대표적인 데이터베이스 시스템이다. 윈도우 환경에서 가장 많이 사용되고 있으며 비주얼 베이직과의

직접적인 프로그래밍도 가능하다. Window 2000 서버나 Windows 2003 서버를 사용하는 시스템에서 활용되고 있다.

- DB II

IBM 의 DB2 (Data Base 2) 는 1983년에 발표된 상업용 관계 데이터베이스 관리 시스템으로서 MVS/XA와 MVS/370 운영체제

에서 사용되며, SQL 을 데이터 언어로 사용하여 다수의 사용자들이 여러 개의 관계 데이터베이스를 동시에 접근할 수 있는

대형 데이터 베이스를 위한 시스템이다.

- My-SQL

600만 사용자를 확보하고 있는 다중 스레드, 다중 사용자 형식의 구조질의어 형식의 데이터베이스 관리 시스템

MySQL AB가 관리 지원하고 있으며 이중 라이선스가 적용된다.

최근 썬 마이크로 시스템즈에 85억달러에 인수 되었다.

- 무결성 제약 조건

: 데이터의 정확성 유지, 사용자가 원치 않는 데이터가 저장되는 것을 방지

NOT NULL : NULL 을 허용하지 않음

UNIQUE : 항상 유일무이한 값을 가짐, 중복되지 않고 NULL 포함 가능

PRIMARY KEY : 반드시 존재해야 하고 유일해야 하며 중복되지 않고 NULL 포함도 불가

FOREIGN KEY : 타 컬럼의 값을 참조해야만 함

CHECK : 저장 가능한 데이터 값의 범위나 사용자 조건을 지정

- 조인

: 한개 이상의 테이블에서 데이터를 조회하기 위해 사용

- 서브쿼리

: 하나의 테이블에서 검색한 결과를 다른 테이블에 전달하여 새로운 결과를 검색하는 것

- 트랜잭션

: 데이터 처리의 한 단위

SQL 명령문들을 하나의 논리적인 작업 단위로 처리하는 것

- 락

: 사용자가 동시에 하나의 테이블에 접근해서 DML 문으로 데이터베이스를 변경하게 되면 특정 사용자가 자원을 독점하지

못하도록 하기 위해서 락을 발생시킨다.

한 사용자가 커밋이나 롤백을 수행하여 락을 풀기 전까지 다른 사용자는 무한 대기 상태가 된다.

- 데드락

: A와 B가 서로 물고 물리는 관계이기에 어느 하나가 커밋이나 롤백을 할 수 없는 상태

세이브 포인터까지 자동으로 롤백된다.

- 뷰

: 물리적인 테이블을 근거한 논리적인 가상 테이블

- 시퀀스

: 자동 번호 발생기

- 인덱스

: SQL 명령문의 처리속도를 향상시키기 위해서 컬럼에 대해서 생성하는 오라클 객체

- 롤

: 사용자에게 효율적으로 권한을 부여할 수 있도록 여러 개의 권한을 묶어 놓은 것

- 커서

: SQL*Plus 에서 사용자가 실행한 SQL 문의 단위

암시적 커서 (하나의 결과를 리턴하는 경우 암시적으로 커서에 저장된다.)

명시적 커서 (결과로 얻어지는 행이 여러 개일 경우에는 반드시 명시적인 커서를 사용해야 한다.)

- 저장 프로시저

: 사용자가 만든 PL/SQL 문을 데이터베이스에 저장한 것

복잡한 DML 문들을 저장 프로시저를 호출해서 간단하게 실행할 수 있다.

- 정규화

: 관계형 데이터베이스에서 데이터 일관성, 최소한의 데이터 중복, 최대한의 데이터 안전성을 확보하기 위해 이상현상이 적게

발생하는 작은 Relation 들로 분해하는 방법을 정규화라 한다.

제1 정규화 (1NF)

제1 정규화란 반복 또는 복수값을 갖는 속성을 제거하여 모든 속성은 반드시 하나의 값만을 갖도록 하는 것입니다.

제2 정규화 (2NF)

제2 정규화란 기본키에 종속되지 않는 속성을 제거하는 것입니다. 즉 모든 속성은 반드시 엔티티 식별자에 전부 종속되

어야 한다는 뜻입니다.

제3 정규화 (3NF)

제3 정규화란 기본키가 아닌 속성에 종속적인 속성을 제거하는 것입니다. 즉 모든 속성은 반드시 엔티티 식별자에 전부

종속되어야 한다는 뜻입니다.

기타 정규형

BCNF (Boyce Codd Normal Form) 라고도 하는 제4 정규형과 제5 정규형도 있지만 이 형식은 실제 디자인에서 거의 고려되

지 않습니다. 이러한 규칙은 무시해도 데이터베이스 디자인의 완벽성은 덜하겠지만 기능적으로는 영향이 없습니다.

*ORM (Object Relational Mapping)

DB와 객체와의 관계를 맵핑시켜 퍼시스턴스 로직처리를 도와주는 프레임워크입니다.

말그대로 데이타 베이스의 테이블과 자바 객체를 맵핑(xml같은 파일에 맵핑정보를 기술)하여

데이타 베이스에 CRUD(생성,조회,수정,삭제)작업을 도와주는 역할을 합니다.

자바로 데이타 베이스에 접근하려면 JDBC를 이용해 쿼리문을 모두 입력해야 합니다.

예를 들어

insert하기위해 dao에서 쿼리문을 모두 세팅해 excute시켜야 합니다.

Hibernate에서는 XXXX.save(dataTO) 이렇게 save라는 메소드를 이용하면

맵핑정보를 읽어 dataTO에 담겨있는 데이터를 디비에 저장하게 됩니다.

이렇게 Persistance 티어에 생산성을 높여 주는 프레임워크가 ORM 프레임워크고,

대중에 유명한것들중에 iBatis와 hibernate이 있습니다.

*Ioc(Inversion of Control) 또는DI(Dependency Injection) (혼용해서 쓰는듯?)

자바 초기에는 객체 생성 및 의존관계에 대한 모든 제어권이 프로그래머에게 있었다.

서 블릿과 EJB가 등장한 후에 개발자들이 서블릿과 EJB를 직접 생성하고 싶어도 이 객체들을 직접 생성하여 제어할 수 없게 되어 버렸다. 객체 생성에 대한 제어권이 컨테이너에게 넘어가면서 객체의 생명주기를 관리하는 권한 또한 컨테이너들이 전담할 수 밖에 없게 된것이다.

IoC에서 이야기하는 제어(권)의 역전이란 객체의 생성에서부터 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미한다.

*Web server와Web application server 의 차이

웹서버에서 화면을 동적으로 보여주기 위해선
여러가지 로직이 들어가지요... 그런데
한 서버에 로직이 집중되어 있다보니 당연히 무거워지고
속도 및 보안 같은 퍼포먼스에 문제점이 생기게 됬습니다.
이런것 때문에 여러개의 서버를 병렬로 처리하는 방법을 쓰기도 하지만
이것보다는 구조적으로 편중된 로직을 분리해서 나누자는 것이죠.
그래서 화면에 뿌려주는 로직(Presentation Logic)은 웹서버(Servlet Engine)에,
실제 돌아가는 로직(Business Logic)은 WAS(Web Applications Server)에서
일을 나누어 역할분담 시키는 것입니다... 이것은 컴포넌트의 유용성과도 밀접한데,
큰 로직을 한번에 돌리는 것보단 쪼개서 여러번 돌리는게 시스템 부하가 적고
신뢰도가 높아지는 원리입니다..
보통 미들웨어라 불리우는 WAS에서는 Component 형식의 Application(EJB)를
많이 쓰구요 이런 구조를 분산시스템 또는 분산 Archtecture 라고도 합니다. (출처 지식인~)

*Web server와JSP컨테이너의 차이

웹서버는 클라이언트/서버 모델과 웹의 HTTP를 사용하여 웹 페이지가 들어 있는 파일을 사용자들에게 제공하는 프로그램이다.

- 웹서버와 JSP컨테이너는 별개라고 할수도 아니라고 할수도 있다. 대부분 JSP로 만들어진 사이트는 웹서버와 JSP컨테이너를 탑재하고 있는 WAS를 연동해 사용하기 때문이다.

쉽 게 말하면 웹서버가 하는 일은 html,img등 일반 웹문서를 사용자에게 보여주는 역할만을 하지만, JSP컨테이너를 탑재하고 있는 WAS(Web Application Server)는 JSP를 컴파일하여 실행한 결과를 html으로 만들어 보여준다.
웹서버만 구축되어 있는 서버는 웹페이지,이미지,영상등을 전달해 줄수는 있지만,
JSP페이지를 컴파일해 동적인 페이지를 생성하지는 못한다. 이를 위해서는
JSP컨테이너가 탑재되어 있는 WAS가 필요한 것이다.
웹서버는 웹문서를, WAS는 JSP 페이지를, 이런식으로 양분하여 역할분담을
함으로 서버에 부담되는 양을 줄이고자 함이다.

유닉스를 능숙하게 사용하기: 몇 번만 클릭하자

전문가처럼 명령행 편집하기

컴퓨터와 인터페이스하는 방식은 끊임없이 변화하고 있습니다. 한 때 명령행 인터페이스만 제공했던 운영체제는 그래픽 프론트엔드로 이동해왔습니다. 하지만 운영체제를 위대하게 만든 원천에서 벗어난 결과가 항상 올바른 방향을 지향했다고 보기는 어렵습니다. IBM?? AIX?? 운영체제는 안정성, 기능성, 견고성이라는 가장 중요한 철학을 지키고 있습니다. 강력한 명령행 인터페이스(CLI, Command Line Interface)를 유지하는 방법으로 AIX는 이런 철학을 지켜왔습니다. CLI 사용법을 배운 적이 없거나 기초를 다시 한번 되새김질할 필요가 있다면 이 기사를 읽어보기 바랍니다.

컴퓨터와 인터페이스하는 방식은 끊임없이 변화하고 있다. 한 때 명령행 인터페이스로 시작했던 운영체제는 그래픽 프론트엔드로 이동했다. 하지만 종종 운영체제를 구성하는 블록에서 벗어난 결과가 항상 올바른 방향을 지향했다고 보기는 어렵다. 종종 그래픽 사용자 인터페이스(GUI) 이동은 기능 상실을 의미한다. 또한 사용자는 작업 대상 컴퓨터에 대해 뭔가 더 익히기를 꺼려하는 경향이 있다. 다행히도 AIX 운영체제는 다른 유닉스(UNIX??)/리눅스(Linux??) 시스템과 마찬가지로 컴퓨터 운영체제에서 안정성, 기능성, 견고성이라는 가장 중요한 철학을 지켜왔다.

다양한 유닉스와 리눅스 회사는 운영체제의 CLI 이면에 숨겨진 중요성을 확실히 깨닫고 있다. 자동화, 사용자를 위한 작업 편의성 등의 이유 때문에 사용자는 CLI라는 복잡한 도구를 잊어버렸거나 결코 배우지 않으려고 한다. 이 기사에서는 CLI에 익숙하지 않은 사용자와 관리, 개발, 일반 유닉스 컴퓨팅에 CLI가 중요한 이유를 기억하기 위해 주의 환기가 필요한 사용자를 위해 CLI를 설명한다.

명령행이 무엇인가?

컴퓨터로 작업할 때, 실제로 어디서 작업하는지를 이해해야 한다. 유닉스나 리눅스에서 작업해봤다면, 셸이나 명령행이라는 용어를 분명히 들어봤을 것이다. 두 용어는 같은 뜻으로 사용될 수 있으며, 현재 사용 중인 실제 유닉스 셸을 지칭한다. 유닉스에서 셸이라는 용어는 명령어를 입력하거나 기능을 수행하기 위해 사용하는 인터페이스를 지칭한다.

사용자가 콘솔이나 네트워크를 통해 유닉스 시스템에 로그인하면, (/etc/passwd에서) 정의한 셸이 떠서 사용자 환경이 환경 설정 파일을 통해 설정되면(이 기사 후반부에 다룬다), 사용자는 셸에서 명령을 수행할 준비가 끝난다. 사용자가 셸을 통해 명령어를 명령행에서 입력할 때, 사용자는 사용자나 프로그램이 제공하는 입력 수단인 표준 입력(stdin)만 볼 수 있다. 사용자가 엔터나 리턴을 누르면, stdin은 실행을 위해 셸로 보내지며, 사용자는 표준 출력(stdout)이나 표준 오류(stderr)를 통해 결과를 받는다. 표준 출력이나 표준 오류는 결과 방향을 지정하는 상황에 따라 달라진다(예: 사용자 화면, 파일, 프린터). stdout이라는 용어는 실행된 프로그램이 돌려주는 출력 자료를 의미하며, stderr은 프로그램 실행 도중이나 완료 후에 일어난 오류를 의미한다. 사용자는 하나 또는 여러 명령을 처리하는 과정에서 모든 저수준 코드 수행 결과를 보지 못하며 아주 간략화된 입력, 출력, 오류만 볼 수 있다. 이런 이유 때문에, 사용자가 정상으로 로그인해 실행한 프로그램은 셸을 부른다. 셀은 운영체제에서 제공하는 저수준 호출을 남김없이 감추기 때문이다.



셸의 역사

유닉스 셸은 진화와 발전을 거치며 대략 35년 동안 사용되어 왔으며, 여전히 기세가 꺾이지 않고 있다. 1971년에 셸은 AT&T 벨 연구소에 근무하던 켄 톰슨이 첫 번째 유닉스 셸인 톰슨 셸을 만들면서 세상에 등장했다. 자료 방향 재지정과 같은 톰슨 셸이 제공하는 기본 기능은 오늘날까지도 셸에 존재한다. 물론 톰슨 셸은 파이프( | ), 셸 스크립트 기능, if 조건문과 같은 오늘날 유닉스 사용자가 매일 사용하는 몇몇 중요한 내장 기능이 빠져있다.

이런 결과로 인해 톰슨 셸은 1977년에 보른 셸(sh)로 대체된다. AT&T 벨 연구소에 근무하던 스티븐 보른이 만든 보른 셸은 유닉스 버전 7(V7)을 위한 기본 셸이 된다. 보른 셸은 유닉스의 미래로 한 걸음 크게 내디뎠다. 보른 셸 덕택으로 사용자는 셸 스크립트를 작성할 수 있었으며, 변수에 정보를 저장하고 공개하며, 파일 기술자를 제어하고 신호 처리를 제어하고 for 반복문과 case 문과 같은 구문을 활용할 수 있었다. 심지어 보른 셸이 만들어진 지 30년이 지났지만 여러 현존하는 유닉스 시스템에 탑재되어 있으며, 오늘날 여러 유닉스 시스템에서는 슈퍼유저(즉 root)를 위한 기본 셸로 지정되어 있다.

지난 30년 동안, 유닉스 셸에는 변경과 개선이 있었다.이런 결과로 여러 가지 셸이 만들어졌다. 그림 1 은 몇몇 유닉스 셸의 가계도를 보여준다. 그림이 완벽하지는 않지만, 주요 셸과 여기서 파생된 셸 사이에 연결 관계를 보여준다.


그림 1. 유닉스 셸 가계도





콘 셸

콘 셸(ksh)은 1982년 AT&T 벨 연구소에 근무하던 데이비드 콘이 처음 개발했다. 다른 셸과 마찬가지로 콘 셸은 보른 셸(sh)과 하위 호환성을 유지하며, 25년이라는 경험을 토대로 튼튼하고 안정적이고 신뢰할 만한 셸로 발전을 거듭해왔다. IBM은 콘 셀을 AIX의 기본 셸로 사용한다. 콘 셸에는 버전이 두 가지가 있으며, AIX는 둘 다를 포함한다.

AIX에서 일반 사용자를 위한 첫 번째이자 기본 셸은 표준 ksh이다. 콘 셸은 운영체제의 국제적인 표준인 POSIX(Portable Operating System Interface for Computer Environments)를 따른다.

AIX에서 사용할 수 있는 두 번째 콘 셸은 개선된 콘 셸로 ksh93으로 부른다. 표준 콘 셸에 여러 가지 멋진 기능을 추가한 ksh93은 다음과 같은 기능을 포함한다.

  • 수학 연산 개선
  • 복합 변수
  • 복합 대입
  • 연상 배열
  • 변수 이름 참조
  • 매개 변수 확장
  • 특정 변수에 결합된 함수
  • 함수 환경 지정
  • PATH 검색 규칙
  • 셸 히스토리
  • 추가 내장 명령어

ksh로 명령행 환경 설정하기

ksh로 명령행을 편집하는 방법을 살펴보기 전에 환경을 설정해야 한다. 개인 기호에 따라 콘 셸을 설정하는 방법은 상대적으로 간단하다. ksh로 로그인했다면 set 명령어에 -o 스위치를 붙여 현재 설정 상태를 확인하자.


# set -o

Current option settings are:
allexport off
bgnice on
emacs off
errexit off
gmacs off
ignoreeof on
interactive on
keyword off
markdirs off
monitor on
noexec off
noclobber off
noglob off
nolog off
notify off
nounset off
privileged off
restricted off
trackall off
verbose off
vi off
viraw on


각 설정값에 대한 간략한 설명을 정리했다( man set 으로 세부 설명을 찾아볼 수 있다).

  • allexport : 지금부터 정의된 모든 변수를 자동으로 외부에 공개한다.
  • bgnice : 배경에서 동작하는 모든 프로세스를 더 낮은 우선 순위로 돌린다.
  • emacs : 입력한 명령행 텍스트를 편집할 때, emacs 스타일 인라인 편집기를 사용한다.
  • errexit : 명령어가 0이 아닌 다른 종료 상태로 끝나면, ERR 트랩을 수행한다(물론 이 트랩이 설정되었으며 존재할 경우에만).
  • gmacs : 입력한 명령행 텍스트를 편집할 때, gmacs 스타일 인라인 편집기를 사용한다.
  • ignoreeof : EOF 문자를 무시하고 셸을 끝내지 않는다. 사용자가 종료를 원하면 exit 명령어를 입력하거나 컨트롤-D를 11번 누른다.
  • keyword : 명령어 앞에 오는 인수뿐만 아니라 명령어 중간에 나오는 모든 변수 대입 구문도 인식한다.
  • markdirs : 파일 이름 대체 과정에서 순 방향 슬래시( / )를 디렉터리 끝에 붙인다.
  • monitor : 모든 작업을 별도 프로세스로 배경에서 수행하며, 프로세스가 끝나면 stdout으로 출력하는 방법으로 사용자에게 결과를 알린다.
  • noexec : 명령을 수행하지 않는다. 대신 구문 오류만 검사한다.

    주의 : 비활성 셸에서 시도할 경우 이 옵션은 무시된다.

  • noclobber : 이 플래그는 출력 방향 지정을 바꿀 때 현존하는 파일을 잘라버리지 못하도록 막는다. 이 옵션을 켜더라도, > 와 파이프 기호( >| )를 사용하면 파일을 잘라버릴 수 있다.
  • noglob : 파일 이름 치환 기능을 비활성화한다.
  • nolog : 이 옵션을 켜면, 히스토리 파일에 함수 정의가 저장되지 않는다.
  • nounset : 치환 과정에서 설정되지 않은 모든 매개변수는 오류로 반환된다.
  • restricted : 제약이 가해진 셸을 수행한다. 사용자는 디렉터리를 변경하지 못하며 SHELL, ENV, PATH 변수도 변경하지 못하며 경로 이름에 선행 슬래시 문자( / )가 들어있는 명령어도 수행하지 못하며 출력 방향 재지정도 불가능하다.
  • trackall : 각 명령을 처음 수행할 때 경로를 기억해 다음 번 수행 속력을 높이는 추적 앨리어스로 기록한다.
  • verbose : 모든 입력 행 내용을 셸이 읽은 그대로(즉 확장하지 않고) stdout으로 출력한다.
  • vi : 입력한 명령행 텍스트를 편집할 때, vi 스타일 인라인 편집기를 사용한다.
  • viraw : 입력하는 각 문자를 마치 vi 편집기에서 입력한 듯이 수행한다.
  • xtrace : 실행되는 모든 명령어와 인수를 표준 출력으로 내보낸다(즉 확장한 상태로).

내장 명령어 집합에서 옵션을 켜려면 -o 스위치를 사용하자. 마음이 바뀌면 -o 대신 +o 스위치를 사용해 설정한 옵션을 끌 수 있다.

이 기사에서 초점을 맞추는 핵심 옵션은 인라인 편집기 스위치다. 사람마다 vi, emacs, gmacs 편집기 중 하나를 선호한다. 콘 셸은 세 가지 편집기 모두를 수용한다. 하지만 이 기사에서는 vi 편집기에 초점을 맞춘다. 인라인 편집기 옵션을 vi 로 설정하기는 쉽다. 현재 설정을 보기 위해 사용했던 명령어에 다음과 같이 옵션을 입력하기만 하면 된다.


# set -o vi


이제 끝났다. 설정 값을 비교하려면 다시 한번 현재 설정을 살펴본다.

# set -o

Current option settings are:
allexport off
bgnice on
emacs off
errexit off
gmacs off
ignoreeof on
interactive on
keyword off
markdirs off
monitor on
noexec off
noclobber off
noglob off
nolog off
notify off
nounset off
privileged off
restricted off
trackall off
verbose off
vi on
viraw on
xtrace off




콘 셸 vi 인라인 편집기 활용하기

이제 vi 인라인 편집기를 사용하도록 셸을 설정했으므로, 테스트할 시간이 왔다.

명령행에서 텍스트 변경하기

지금 명령행에 글자를 입력하면서 vi 편집기에서 삽입 모드에 들어와 있다고 생각하기 바란다. 실수를 하거나 실행할 명령어에 뭔가를 추가할 필요가 있다면, ESC 키를 누르고 삽입 모드를 빠져나가 명령 모드로 돌아간다.

예를 들어, 현재 작업 디렉터리 내용이 다음과 같다고 가정하자.

# ls
fileA fileAA fileAAA fileAB fileABA fileABB fileB fileBAA fileBB fileBBB


fileAA로 시작하는 파일을 찾아 삭제하기를 원한다면 다음과 같은 명령을 내린다.

# find . -name "fileAB*" -exec rm {} \;

입력한 행을 수행하기 전에, fileAA 대신에 fileAB 를 입력했다는 실수를 찾았다! 걱정할 필요 없다. 삽입 모드에서 빠져나와서 명령 모드로 들어가서 잘못된 글자로 커서를 옮기고 글자를 바꾸자. 이 모든 작업은 vi 명령으로 수행한다. 인라인 편집기에서 삽입 모드에 들어있는 상태에서 명령 순서를 차례로 쪼개보자.

  1. ESC를 눌러 명령 모드로 전환한다.
  2. vi 스타일 이동 명령(H키는 커서를 왼쪽으로 움직인다)을 사용해 문자열 "fileAB*"에서 B로 커서를 이동한다.

    주의 : vi에서 화살표 키에 익숙하다면 커서를 움직이는 과정에서 키보드의 어떤 키를 누르는지 익혀두는 편이 현명하다. TERM 유형이 달라 화살표 키로 원하는 결과를 얻지 못할 가능성이 있기 때문이다.

    • h : 왼쪽
    • l : 오른쪽
    • k : 위쪽
    • j : 아래쪽
  3. vi 스타일로 '한 글자 변경' 명령을 사용해 B를 A로 바꾼다(다시 말해, R을 누른 다음에 A 를 입력한다).

작업을 검토해 원하는 결과를 얻었다면 명령어를 수행하기 위해 엔터를 누르자.

# find . -name "fileAA*" -exec rm {} \;
# ls
fileA fileAB fileABA fileABB fileB fileBAA fileBB fileBBB

파일 이름 완성

콘 셸에서 vi 인라인 편집기를 유용하게 사용하는 또 다른 방법은 파일 이름 완성이다. 명령어를 수행할 때, 종종 stdin이나 stdout(또는 stderr) 내용을 파일에 쓰기 위해 매개변수로 사용할 파일을 입력하는 경우가 있다. 파일 이름이 길고, 비슷한 이름이 여러 개 존재하거나 전체 파일 이름을 기억하지 못한 경우가 있다. 이럴 때가 바로 파일 이름 완성 기능이 도움을 주는 상황이다. 파일 이름을 입력할 때, 절반 정도 진행한 다음에 ESC키를 누른 다음에 \ (역슬래시)를 누르면 된다. 이런 방법은 편리할 뿐만 아니라 시간도 상당히 절약해준다.

예를 들어, AIX에서 /etc/filesystems를 보기를 원했는데, 전체 파일 이름을 잊어버렸다. /etc에 있다는 사실을 알고 파일 이름이 무엇으로 시작하는지도 알지만 그게 전부다. 단순히 view /etc/file 을 입력하고 ESC-\를 누르면 만세! ksh이 대신해 행을 완성해준다. 명령행에는 view /etc/filesystems 라는 완성된 내용이 적혀 있다.

디렉터리 구조에서도 똑같은 작업이 가능하다. 디렉터리 이름도 파일 이름과 동일하게 취급하기 때문이다.

명령어 히스토리 확인과 변경

유닉스 시스템에서 프로세스를 감시하거나 다른 기능을 수행하는 과정에서 동일한 명령을 계속해서 입력한 경우가 있는가? 매번 다시 입력하는 대신에 콘 셸은 검토를 위한 내장 명령어 히스토리 기능을 제공한다. 인라인 편집기를 vi 로 설정하면, ksh은 사용자가 수행한 명령어 히스토리를 꺼내 명령행에 입력한 다른 텍스트로 변경하는 기능을 지원한다. 시스템 설정에 따라 해당 세션만 기억할 수도 있다.

변수 HISTFILE에 파일 이름을 정의했다면, ksh은 히스토리에서 끄집어내어 원본 명령어를 다시 수행하거나 명령을 수정할 기회를 제공한다. 예를 들어, 여기에 예제 $HISTFILE에서 마지막으로 실행한 명령 열 개를 뽑았다.

# tail -10 $HISTFILE
ls
cd ~cormany/testdir/dirA
./fileA 1>fileA.out 2>fileA.errors
pwd
ps --fu cormany
df --k .
ps --fu cormany
find . --name “fileA.out” --ls
find . --name “fileA.errors” --ls
tail -10 $HISTFILE

명령행에 있을 때 vi 인라인 편집기에서 명령행 모드로 들어가려면 ESC만 누르면 된다. 그리고 나서 K를 눌러 마지막으로 수행한 명령을 끌어내자. 여전히 명령 모드에 있기 때문에 K를 계속해서 눌러 명령어 히스토리 하나 위로 가거나 J를 눌러 명령어 히스토리 하나 아래로 갈 수 있다.

명령 모드 커서 이동을 간편하게 기억하기 위해, 명령행 프롬프트에서 ESC를 누를 때, vi에서 일반 파일로 $HISTFILE을 읽었다고 생각하자. vi 편집기에서 K키는 한 행 위로 올라가고, J키는 한 행 아래로 내려간다. 동일한 $HISTFILE에서 ESC-J를 누르면 $HISTFILE 편집 과정을 시각적으로 보여주며 파일의 아래쪽에 커서가 위치한다. 행은 tail -10 $HISTFILE 이 될 것이다. J를 다시 한번 누르면 편집 중인 $HISTFILE에서 한 행 올라가 행은 find . -name "fileA.errors" --ls 가 될 것이다.

그림 2 는 ksh vi 인라인 편집기 명령 모드 움직임을 일반 vi 명령 모드 커서 움직임과 비교한 간단한 "커닝 페이퍼"를 보여준다.


그림 2. vi 명령 모드 커닝 페이퍼





명령행 vs 셸 스크립트

셸 스크립트를 사용해야 하는 경우가 있고, 명령행을 사용해야 하는 경우가 있다. 주기적으로 반복해서 작업을 수행하거나 항상 사용자에게 명령을 입력하게 만드는 대신 자료 처리를 요구하는 등 요청 작업이 복잡하다면 셸 스크립트가 유용하다. 그렇지 않고 한번만 명령을 사용하거나 상대적으로 간단한 명령을 수행할 경우 명령행으로 간단하게 작업을 처리할 수 있다.

예를 들어, 이 디렉터리 목록을 살펴보자.

# ls
fileA.tar.gz fileAA.tar.gz fileB.tar.gz fileBB.tar.gz

단순히 파일 압축을 해제한 다음에 bzip2로 다시 압축하고 ATC-AIX2로 전송하기를 원한다면 셸 스크립트를 작성하는 대신에 명령행으로 처리할 수 있다. 셸 스크립트를 여러 명령행 항목을 한번에 입력하는 방법으로 생각하자. 상식적으로 셸 스크립트가 정말로 의미하는 바를 제대로 나타내기 때문이다. 명령행에서 명령어를 입력할 때, 이런 작업은 스크립트에 명령어를 입력한 다음에 이 스크립트를 수행하는 경우와 똑같다.

디렉터리를 돌며 gz로 끝나는 파일만 찾아 압축을 해제한 다음에 bzip2로 다시 압축하고 scp 명령으로 파일을 ATC-AIX라는 목적지 서버로 옮기기를 원한다. 반복은 스크립트와 마찬가지로 명령행에서도 멋지게 동작한다. loop...if 조건문, case switch 문, 기타 코드 블록 문을 시작할 때 동작 중인 ksh은 단순히 커서를 다음 행으로 내리고 프롬프트를 $PS2로 바꿀 것이다. 코드 블록이 완료되면, 코드 블록을 수행하고 사용자에게 $PS1 프롬프트를 보여준다.

다시 정리하면 다음과 같다.

  • $PS1 프롬프트 : 다음 명령어를 기다림
  • $PS1 프롬프트 : 코드 블록 시작
  • $PS2 프롬프트 : 코드 블록 계속
  • $PS2 프롬프트 : 코드 블록 계속
  • $PS2 프롬프트 : 코드 블록 끝
  • 코드 블록 수행
  • $PS1 프롬프트 : 다음 명령어를 기다림

변수 PS2의 기본값은 > 이다. 압축을 풀고 다시 압축하는 직전 예제로 돌아가서, ksh 명령행에서 다음과 같이 간단하게 입력할 수 있다.


# for _FNAME in 'ls -1 *.gz'
> do
> gzip -d ${_FNAME}
> bzip2 ${_FNAME%*.gz}
> scp ${_FNAME%*.gz}.bz2 cormany@ATC-AIX2:/home/cormany
> done


코드 블록을 끝낸 다음에(다시 말해 done 으로 반복을 종료한 다음에) 엔터를 누르면 반복을 시작한다. 명령행에서 입력한 반복은 .gz로 끝나는 모든 파일을 현재 디렉터리에서 찾은 다음에 압축을 풀어 bzip2로 다시 압축하며, ATC-AIX2에 있는 /home/cormany 디렉터리로 전송한다. 아주 간단하다.




결론

이 기사를 읽고 나면, 예전에 몰랐던 콘 셸 활용법을 이해할 것이다. 명령행을 완벽하게 터득하면 작업을 단순하게 만들 수 있으며, 셸과 명령행에 질질 끌려다니는 대신에 앞서나가기 위한 방법을 제대로 이해하는 데 도움이 된다.

필자소개

Adam Cormany는 National Data Center의 관리자로 현재 일하고 있다. Cormany는 유닉스 시스템 엔지니어, 유닉스 관리자, Scientific Games Corporation에서 운영 관리자를 맡아왔다. Cormany는 10년 넘게 솔라리스, 레드햇 리눅스 관리자는 물론이고 AIX도 광범위하게 다뤘다. Cormany는 pSeries AIX 시스템 관리 분야에서 IBM eServer?? 공인 전문가다. 관리 업무 이외에, Cormany는 배시, csh, ksh을 사용한 셸 스크립트와 C, PHP, 펄 프로그래밍에도 광범위한 지식을 습득했다.

제공 : DB포탈사이트 DBguide.net

PHP 프로그램에서 구글 캘린더 사용하기

PHP 응용 프로그램에서 구글 캘린더 정보를 가져와 통합하자

구글 캘린더는 웹 응용 프로그램 개발자에게 사용자가 입력한 내용과 이벤트 정보를 REST 기반 개발자 API로 가져오는 방법을 제공합니다. PHP의 SimpleXML 확장 기능과 젠드(Zend) 프레임워크의 GData 라이브러리는 구글 캘린더 API가 생성한 XML 피드를 처리해 PHP 응용 프로그램을 만들기에 이상적인 기술입니다. 이 기사에서는 1) 구글 캘린더 데이터 API를 소개하고, 2) 사용자가 만든 캘린더를 살펴보고, 3) 캘린더 이벤트를 추가/갱신하고, 4) 키워드로 캘린더를 검색해 봅니다.

소개

오랫 동안 내가 정기적으로 사용한 유일한 일정 관리 도구는 팜파일럿에 딸려나온 일정 관리 소프트웨어였다. 하지만 지난 몇 년에 걸쳐 나는 점차 구글 캘린더 애용자가 되었다. 웹-웨어(Web-ware)라는 사실 외에도 이벤트 소식을 공유하고, 초청장과 응답을 관리하고, 다양한 이벤트 유형을 처리하기가 편리한 덕분이다.

자주 쓰이는 약어 소개

API: Application Programming Interface
HTTP: Hypertext Transfer Protocol
PHP: PHP: Hypertext Preprocessor
REST: Representational state transfer
RSS: Really Simple Syndication
URL: Uniform Resource Locator
XML: Extensible Markup Language

개발자 입장에서도 구글 캘린더는 흥미를 자극한다. 구글 캘린더 데이터 API를 사용하면 공개 캘린더나 개인 캘린더에 있는 정보를 바탕으로 새로운 응용 프로그램을 제작하기가 아주 쉽다. 구글 캘린더 API는 REST 모델을 따르며, XML을 인식하는 개발 도구라면 어디서나 사용할 수 있다. 게다가 이미 많은 프로그래밍 언어에서 (내가 가장 좋아하는 PHP에서도) 클라이언트 라이브러리를 제공한다.

이 기사에서는 구글 캘린더 데이터 API를 소개하고, PHP 프로그램에서 캘린더 정보를 통합하고 사용하는 방법을 살펴본다. 구체적으로는 다음과 같은 내용을 다룬다.

  • 사용자 공개 노트북에서 이벤트를 가져오는 방법
  • 새 이벤트를 추가하는 방법
  • 기존 이벤트를 수정하고 삭제하는 방법
  • 키워드나 날짜 범위로 이벤트를 검색하는 방법

자, 이제 시작해 보자!!


구글 캘린더 데이터 API 이해하기

PHP 코드로 뛰어들기 전에 구글 캘린더 데이터 API부터 이해하자. 여느 REST 기반 서비스와 마찬가지로, 구글 캘린더 데이터 API 역시 (XML 형식으로 인수가 지정된) HTTP 요청을 받아 XML 형식으로 만들어진 응답을 반환한다. 클라이언트에서는 이 XML 응답을 분석한다. 구글 캘린더 데이터 API는 항상 요청한 정보를 포함한 Atom 피드나 RSS 피드로 응답을 보낸다.

구글 캘린더 API가 반환하는 피드는 흔히 (일반적인 응용 프로그램 입장에서 필요 이상으로) 상당히 많은 정보를 포함한다. 예를 들어, 구글 캘린더 계정으로 로그인한 후 캘린더 설정에서 비공개 주소를 찾는다. 이 주소는 다른 사람과 공유해서는 안 되며, 권한을 먼저 요청하지 않은 상태에서는 캘린더 피드에 읽기 전용 접근만 허용하며, http://www.google.com/calendar/feeds/userid/private-magicCookie/basic 형태다. 브라우저 주소란에 자신의 비공개 주소를 입력해보자(아니면 HTTP 클라이언트를 통해 피드를 GET 요청으로 보낸다). 그러면 Listing 1과 같은 피드가 표시된다.


Listing 1: 구글 캘린더 피드 예

<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'> <id>http://www.google.com/calendar/feeds/user@gmail.com/ private-cook!e/basic</id> <updated>2008-06-13T19:15:18.000Z</updated> <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/> <title type='text'>Joe User</title> <subtitle type='text'>Joe User</subtitle> <link rel='alternate' type='text/html' href='http://www.google.com/calendar/embed?src=user@gmail.com'/> <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.google.com/calendar/feeds/user@gmail.com/private-cook!e/basic'/> <link rel='http://schemas.google.com/g/2005#batch' type='application/atom+xml' href='http://www.google.com/calendar/feeds/user@gmail.com/private-cook!e/basic/batch' /> <link rel='self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/user@gmail.com/private-cook!e/basic? max-results=25'/> <author> <name>Joe User</name> <email>user@gmail.com</email> </author> <generator version='1.0' uri='http://www.google.com/calendar' >Google Calendar</generator> <openSearch:totalResults>4</openSearch:totalResults> <openSearch:startIndex>1</openSearch:startIndex> <openSearch:itemsPerPage>25</openSearch:itemsPerPage> <gCal:timezone value='Asia/Calcutta'/> <entry> <id>http://www.google.com/calendar/feeds/user@gmail.com/ private-cook!e/basic/xxxxxxxx</id> <published>2008-06-12T08:49:38.000Z</published> <updated>2008-06-13T19:06:21.000Z</updated> <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/> <title type='html'>Swim party</title> <summary type='html'>When: Sat Jun 21, 2008 12pm to 3:30pmIST<br><br>Event Status: confirmed</summary> <content type='html'>When: Sat Jun 21, 2008 12pm to 3:30pm IST<br /><br />Event Status: confirmed</content> <link rel='alternate' type='text/html' href='http://www.google.com/calendar/event?eid=cGxwbHExOYHHDlOHQ4ZjA yMGMgdmlrcmFtLm1lbG9uZmlyZUBnb29nxmNvbQ' title='alternate'/> <link rel='self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/user@gmail.com/ private-cook!e/basic/ddddddddddddddd/> <author> <name>Joe User</name> <email>user@gmail.com</email> </author> </entry> <entry> <id>http://www.google.com/calendar/feeds/user@gmail.com/ private-cook!e/basic/yyyyyyyyyyyyyyyy</id> <published>2008-06-12T08:48:30.000Z</published> <updated>2008-06-12T08:48:46.000Z</updated> <category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2005#event'/> <title type='html'>Dinner with the gang</title> <summary type='html'>When: Wed Jun 11, 2008 7pm to 9:30pmIST<br><br>Event Status: confirmed</summary> <content type='html'>When: Wed Jun 11, 2008 7pm to 9:30pm IST<br /><br />Event Status: confirmed</content> <link rel='alternate' type='text/html' href='http://www.google.com/calendar/event?eid= MmhpYmV2cmowMM2kam9lZDQgdcmFtLm1lbG9uZmlyZU4858Bnb29nbGVtYWlsLmNvbQ' title='alternate'/> <link rel='self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/user@gmail.com/private-cook!e/ basic/hhhhhhhhhhhhhhhhh'/> <author> <name>Joe User</name> <email>user@gmail.com</email> </author> </entry> <entry> ... </entry></feed>



모든 캘린더 피드는 <feed> 엘리먼트가 루트다. <feed> 엘리먼트는 여러 <link> 엘리먼트와 여러 <openSearch:> 엘리먼트를 포함한다. <link> 엘리먼트는 피드의 다른 버전 URL을 포함하며, <openSearch:> 엘리먼트는 요약 통계를 포함한다.

가장 바깥에 있는 <feed> 엘리먼트는 <entry> 엘리먼트를 한 개 이상 포함하며, 각 <entry> 엘리먼트는 좀 더 세부적인 캘린더 이벤트 정보를 포함한다. 이벤트 정보로는 제목, 설명, 작성일, 최종 수정일, 이벤트 피드 URL, 이벤트 작성자가 있다. 각각은 <title>, <summary>, <published>, <updated>, <link>, <author> 엘리먼트에 들어 있다.

SimpleXML로 이벤트 목록 가져오기

다음은 PHP를 사용하여 구글 캘린더 피드를 처리하는 예제다. Listing 2는 Listing 1 피드를 받아 SimpleXML로 필요한 정보를 추출한 후 형식에 맞춰 웹 페이지에 표시한다.


Listing 2: SimpleXML로 이벤트 목록 가져오기


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Listing calendar contents</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <?php $userid = 'username%40googlemail.com'; $magicCookie = 'cook!e'; // 피드 URL 생성 $feedURL = "http://www.google.com/calendar/feeds/$userid/private-$magicCookie/basic"; // 피드를 SimpleXML 객체로 읽음 $sxml = simplexml_load_file($feedURL); // 이벤트 개수 얻기 $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/'); $total = $counts->totalResults; ?> <h1><?php echo $sxml->title; ?></h1> <?php echo $total; ?> event(s) found. <p/> <ol> <?php // 범주에 속한 항목 순회 // 각 항목 세부 내역 출력 foreach ($sxml->entry as $entry) { $title = stripslashes($entry->title); $summary = stripslashes($entry->summary); echo "<li>\n"; echo "<h2>$title</h2>\n"; echo "$summary <br/>\n"; echo "</li>\n"; } ?> </ol> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>




그림 1은 Listing 2로 얻은 결과다.


그림 1. SimpleXML로 가져온 캘린더 이벤트 목록


Listing 2 코드를 살펴보면, simplexml_load_file() 함수는 피드 URL로 요청을 보낸 후 결과를 SimpleXML 객체로 변환한다. 그런 다음, foreach() 루프를 돌면서 <entry>를 하나씩 찾은 후 그림 1과 같은 정보를 추출한다. <entry> 엘리먼트 아래 있는 노드는 SimpleXML 객체 속성으로 참조한다. 예를 들어, <title> 노드는 $entry->title로, <summary> 노드는 $entry->summary로 참조한다.

여기서 사용한 피드 URL은 사용자의 비공개 캘린더 피드를 참조한다. 사용자의 비공개 캘린더 피드는 사용자 전자편지 주소와 매직 쿠키(magic cook!e)라는 문자열을 포함하는데, 인증 과정을 거치지 않고 캘린더 자료를 읽기 전용으로 가져오는 경우에 비공개 주소를 사용한다. 앞서 언급했듯이, 이 피드 URL을 얻으려면 사용자의 구글 캘린더 페이지로 로그인한 후 캘린더 설정에서 URL을 직접 복사해 PHP 스크립트에 추가해야 한다.

젠드 GData 클라이언트 라이브러리로 이벤트 목록 가져오기

매직 쿠키가 확실히 사용하기 편하지만, 일반 PHP 프로그램에서는 다음 두 가지 측면에서 실용적이지 못하다.

  • 캘린더 정보에 읽기 전용 접근만 가능하다.
  • (읽기 외에) 다른 캘린더 작업에는 사용하지 못한다.


캘린더 데이터 API로 (이벤트 수정, 삭제, 추가, 검색 등) 다양한 작업을 수행하려면 사용자 인증이 필요하다. 구글이 인정하는 인증 방법은 AuthSub과 ClientLogin 두 가지다.

사용자 인증 단계를 일일이 구현하기란 매우 번거롭다. 아주 전형적인 인증 단계라도 온갖 시나리오를 처리하려면 상당한 코드가 필요하다. 다행스럽게도 젠드 GData 클라이언트 라이브러리를 사용하면 개발자가 세세한 부분까지 신경쓸 필요가 없다. 젠드 GData 클라이언트 라이브러리는 PHP 응용 프로그램과 구글 데이터 API를 통합하는 과정에서 필요한 세부 사항을 모두 처리한다(라이브러리를 내려받는 링크는 참고자료에서 소개한다). 이 라이브러리는 구글 데이터 API와 연결하는 편리한 객체 지향 인터페이스를 제공하며, 사용자 인증 등 일반적인 작업을 거의 대부분 처리한다. 그러므로 개발자는 구글 데이터 API와 통신하는 방법을 신경쓸 필요 없이 응용 프로그램 자체에 집중할 수 있다.

Listing 3은 젠드 GData 클라이언트 라이브러리를 사용하여 Listing 2와 똑같은 기능을 구현한 코드다.


Listing 3: 젠드 라이브러리로 이벤트 리스트 가져오기

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Listing calendar contents</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <?php // load library require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar'); Zend_Loader::loadClass('Zend_Http_Client'); // 캘린더 서비스를 위해 인증된 HTTP 클라이언트 생성 $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME; $user = "username@gmail.com"; $pass = "pass"; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal); $gcal = new Zend_Gdata_Calendar($client); // 이벤트 목록을 얻기 위해 질의 생성 $query = $gcal->newEventQuery(); $query->setUser('default'); $query->setVisibility('private'); $query->setProjection('basic'); // 캘린더 피드 획득과 해석 // 결과 출력 try { $feed = $gcal->getCalendarEventFeed($query); } catch (Zend_Gdata_App_Exception $e) { echo "Error: " . $e->getResponse(); } ?> <h1><?php echo $feed->title; ?></h1> <?php echo $feed->totalResults; ?> event(s) found. <p/> <ol> <?php foreach ($feed as $event) { echo "<li>\n"; echo "<h2>" . stripslashes($event->title) . "</h2>\n"; echo stripslashes($event->summary) . " <br/>\n"; echo "</li>\n"; } echo "</ul>"; ?> </ol> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>

Listing 3은 가장 먼저 젠드 클래스 라이브러리를 로드한다. 그런 다음, Zend_Http_Client 클래스 인스턴스를 초기화한다. 이 클래스에 필요한 사용자 인증 정보를 제공한 후 캘린더 서비스에 인증된 채널로 연결한다. 인증 채널로 캘린더 서비스에 연결한 후에는 getCalendarEventFeed() 메서드로 캘린더 피드를 가져온다. 이 메서드는 EventQuery 객체를 받는데, 이 객체에 사용자 이름, 피드 유형(public 또는 private), 정보 상세 수준(full 또는 basic) 등 필요한 매개변수를 지정한다. getCalendarEventFeed() 메서드는 XML 문서를 반환하며, PHP 객체로 변환하는 작업을 거친다. 마지막으로 변환된 PHP 객체 속성에서 필요한 정보를 추출하여 HTML 페이지에 표시한다.

그림 2는 Listing 3으로 얻은 결과다.


그림 2. 젠드 GData 클라이언트 라이브러리로 가져온 캘린더 이벤트 목록

새 이벤트 추가하기

지금까지 클라이언트 프로그램에서 이벤트 목록만 가져왔다. 그렇다면 새로운 이벤트는 어떻게 추가할까?

생각보다 어렵지 않다. 구글 캘린더 데이터 API를 사용하면 간단하다. 먼저, XML 형식으로 이벤트 <entry> 엘리먼트를 생성한 후 캘린더 피드에 POST 메서드로 전송하면 그만이다. Listing 4는 새 이벤트를 나타내는 XML 예제다.


Listing 4: 새 이벤트를 나타내는 예제 XML

<atom:entry xmlns:atom="http://www.w3.org/2005/Atom"> <atom:title type="text">Dinner with the gang</atom:title> <gd:when xmlns:gd="http://schemas.google.com/g/2005" startTime="2008-06-23T18:00:00+05:30" endTime="2008-06-23T20:00:00+05:30"/> </atom:entry>

젠드 라이브러리를 사용하면 더욱 간단하다. insertEvent() 메서드만 호출하면 그만이다. insertEvent() 메서드가 Listing 4와 같은 XML을 생성하여 캘린더 피드로 전송한다. Listing 5는 이벤트 관련 정보를 사용자에게 받아 캘린더에 해당 이벤트를 추가하는 코드다. 아래서는 젠드 라이브러리를 사용하여 XML을 생성한다.


Listing 5: 웹 폼을 통해 새 이벤트 추가하기

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Adding calendar events</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <h1>Add Event</h1> <?php if (!isset($_POST['submit'])) { ?> "> Event title: <br/> <input name="title" type="text" size="15" /><p/> Start date (dd/mm/yyyy): <br/> <input name="sdate_dd" type="text" size="2" /> <input name="sdate_mm" type="text" size="2" /> <input name="sdate_yy" type="text" size="4" /><p/> Start time (hh:mm): <br/> <input name="sdate_hh" type="text" size="2" /> <input name="sdate_ii" type="text" size="2" /><br/> End date (dd/mm/yyyy): <br/> <input name="edate_dd" type="text" size="2" /> <input name="edate_mm" type="text" size="2" /> <input name="edate_yy" type="text" size="4" /><p/> End time (hh:mm): <br/> <input name="edate_hh" type="text" size="2" /> <input name="edate_ii" type="text" size="2" /><br/> <input name="submit" type="submit" value="Save" /> </form> <?php } else { // 클래스 로드 require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar'); Zend_Loader::loadClass('Zend_Http_Client'); // 서비스에 접속 $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME; $user = "username@gmail.com"; $pass = "pass"; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal); $gcal = new Zend_Gdata_Calendar($client); // 입력 유효성 검사 if (empty($_POST['title'])) { die('ERROR: Missing title'); } if (!checkdate($_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy'])) { die('ERROR: Invalid start date/time'); } if (!checkdate($_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy'])) { die('ERROR: Invalid end date/time'); } $title = htmlentities($_POST['title']); $start = date(DATE_ATOM, mktime($_POST['sdate_hh'], $_POST['sdate_ii'], 0, $_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy'])); $end = date(DATE_ATOM, mktime($_POST['edate_hh'], $_POST['edate_ii'], 0, $_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy'])); // 이벤트 객체 생성 // 서버에 저장 try { $event = $gcal->newEventEntry(); $event->title = $gcal->newTitle($title); $when = $gcal->newWhen(); $when->startTime = $start; $when->endTime = $end; $event->when = array($when); $gcal->insertEvent($event); } catch (Zend_Gdata_App_Exception $e) { echo "Error: " . $e->getResponse(); } echo 'Event successfully added!'; } ?> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>

Listing 5는 크게 두 부분으로 나뉜다. 하나는 웹 폼이고, 다른 하나는 사용자가 웹 폼에 입력한 정보를 처리하는 PHP 코드다. 그림 3은 웹 폼을 보여준다.

그림 3. 새 이벤트를 추가하는 웹 폼

일단 사용자가 이벤트 정보를 이 폼에 입력한 후 폼을 제출하면 코드 나머지 절반이 실행된다. 가장 먼저, 스크립트는 HTTP 클라이언트를 초기화한다. 이 과정에서 HTTP 클라이언트는 구글 캘린더 데이터 API와 인증된 연결을 연다. 다음으로, 스크립트는 사용자가 웹 폼에 입력한 정보를 검증한다. 이벤트 시작 날짜와 종료 날짜를 확인한 후 모든 날짜를 RFC 3339 형식으로 변환한다.

이벤트 정보를 검증한 후에는 새로운 EventEntry 객체를 생성한다. 이 객체는 캘린더에 삽입할 새 이벤트를 나타내며, newTitle()과 newWhen() 메서드를 제공한다. 스크립트는 두 메서드를 사용하여 이벤트 제목과 시작 날짜와 종료 날짜를 설정한다. 객체 속성을 모두 설정한 후에는 insertEvent() 메서드를 호출하여 구글 서버에 해당 이벤트를 저장한다. 저장한 이벤트는 캘린더에 즉시 표시된다.

그림 4는 캘린더에 새 이벤트를 성공적으로 추가한 후 얻어지는 결과다.

그림 4. 새 이벤트를 추가한 결과

기존 이벤트 삭제하고 수정하기

이벤트를 삭제하기는 더욱 쉽다. 해당 이벤트 URL을 얻은 후 해당 URL에 DELETE 요청을 보내면 그만이다. 젠드 라이브러리에서는 getCalendarEventEntry() 메서드로 이벤트 객체를 얻은 후 객체의 delete() 메서드를 호출한다. 자세한 내용은 Listing 6을 참조한다.


Listing 6: 이벤트 삭제하기

<?php// 클래스 로드require_once 'Zend/Loader.php';Zend_Loader::loadClass('Zend_Gdata');Zend_Loader::loadClass('Zend_Gdata_ClientLogin');Zend_Loader::loadClass('Zend_Gdata_Calendar');Zend_Loader::loadClass('Zend_Http_Client');// 서비스에 연결$gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;$user = "username@gmail.com";$pass = "pass";$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);$gcal = new Zend_Gdata_Calendar($client); // 이벤트 인출// 이벤트 삭제try { $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/ feeds/default/private/full/xxxxxxx'); $event->delete();} catch (Zend_Gdata_App_Exception $e) { echo "Error: " . $e->getResponse();} echo 'Event successfully deleted!'; ?>

이벤트를 수정하는 방법도 비슷하다. 해당 이벤트 URL을 얻은 후 이 URL에 PUT 요청과 수정할 정보를 보내면 그만이다. 젠드 라이브러리에서는 이벤트 객체 속성에 새 값을 설정한 후 save() 메서드로 변경된 값을 이벤트 서버로 보낸다. 자세한 내용은 Listing 7을 참조한다.

Listing 7: 이벤트 수정하기

<?php// 클래스 로드require_once 'Zend/Loader.php';Zend_Loader::loadClass('Zend_Gdata');Zend_Loader::loadClass('Zend_Gdata_ClientLogin');Zend_Loader::loadClass('Zend_Gdata_Calendar');Zend_Loader::loadClass('Zend_Http_Client');// 서비스에 연결$gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;$user = "username@gmail.com";$pass = "pass";$client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal);$gcal = new Zend_Gdata_Calendar($client); // 이벤트 인출// 새로운 이벤트 속성 설정과 이벤트 갱신try { $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/feeds/ default/private/full/xxxxxxxxxxx'); $event->title = $gcal->newTitle($title); $when = $gcal->newWhen(); $when->startTime = $start; $when->endTime = $end; $event->when = array($when); $event->save(); } catch (Zend_Gdata_App_Exception $e) { die("Error: " . $e->getResponse());}echo 'Event successfully modified!'; ?>

캘린더 작업 통합하기

지금까지 익힌 지식을 바탕으로 좀 더 실용적인 프로그램을 구현해 보자. Listing 8은 Listing 3을 개선한 코드로, 각 이벤트에 편집과 삭제 링크를 추가한다. 각 링크는 edit.php와 delete.php 스크립트를 가리키며, (이벤트 목록에서 추출한) 이벤트 식별자를 각 스크립트에 GET 메서드로 넘긴다.


Listing 8: 이벤트 목록 가져오기

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Listing calendar contents</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <?php require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar'); Zend_Loader::loadClass('Zend_Http_Client'); $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME; $user = "username@gmail.com"; $pass = "pass"; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal); $gcal = new Zend_Gdata_Calendar($client); $query = $gcal->newEventQuery(); $query->setUser('default'); $query->setVisibility('private'); $query->setProjection('basic'); try { $feed = $gcal->getCalendarEventFeed($query); } catch (Zend_Gdata_App_Exception $e) { echo "Error: " . $e->getResponse(); } ?> <h1><?php echo $feed->title; ?></h1> <?php echo $feed->totalResults; ?> event(s) found. <p/> <ol> <?php foreach ($feed as $event) { echo "<li>\n"; echo "<h2>" . stripslashes($event->title) . "</h2>\n"; echo stripslashes($event->summary) . " <br/>\n"; $id = substr($event->id, strrpos($event->id, '/')+1); echo "<a href=\"edit.php?id=$id\">edit</a> | "; echo "<a href=\"delete.php?id=$id\">delete</a> <br/>\n"; echo "</li>\n"; } echo "</ul>"; ?> </ol> <p/> <a href="add.php">Add a new event</a><p/> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>

그림 5는 Listing 8을 실행한 결과다.

그림 5. 이벤트 목록을 표시하는 웹 페이지

Listing 9는 delete.php 스크립트 코드다. 이 스크립트는 GET 메서드로 이벤트 식별자를 받아 해당 이벤트를 삭제한다. 구체적인 설명은 Listing 6을 참조한다.


Listing 9: 이벤트 삭제하기

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Deleting calendar events</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <h1>Delete Event</h1> <?php // 클래스 로드 require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar'); Zend_Loader::loadClass('Zend_Http_Client'); // 서비스에 연결 $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME; $user = "username@gmail.com"; $pass = "pass"; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal); $gcal = new Zend_Gdata_Calendar($client); // 이벤트 ID가 존재하면 // 피드에서 이벤트 객체를 얻는다. // 이벤트 삭제 if (isset($_GET['id'])) { try { $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/ feeds/default/private/full/' . $_GET['id']); $event->delete(); } catch (Zend_Gdata_App_Exception $e) { echo "Error: " . $e->getResponse(); } echo 'Event successfully deleted!'; } else { echo 'No event ID available'; } ?> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>

Listing 10은 edit.php 스크립트 코드다.


Listing 10: 이벤트 편집하기

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Updating calendar events</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <h1>Edit Event</h1> <?php // 클래스 로드 require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar'); Zend_Loader::loadClass('Zend_Http_Client'); // 서비스에 연결 $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME; $user = "username@gmail.com"; $pass = "pass"; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal); $gcal = new Zend_Gdata_Calendar($client); // 이벤트 세부 내역 얻기 if (!isset($_POST['submit'])) { if (isset($_GET['id'])) { try { $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/ feeds/default/private/full/' . $_GET['id']); } catch (Zend_Gdata_App_Exception $e) { echo "Error: " . $e->getResponse(); } } else { die('ERROR: No event ID available!'); } // 자료를 사람이 읽을 수 있는 형식으로 변환 // 이 레코드로 웹 폼 생성 $title = $event->title; $when = $event->getWhen(); $startTime = strtotime($when[0]->getStartTime()); $sdate_dd = date('d', $startTime); $sdate_mm = date('m', $startTime); $sdate_yy = date('Y', $startTime); $sdate_hh = date('H', $startTime); $sdate_ii = date('i', $startTime); $endTime = strtotime($when[0]->getEndTime()); $edate_dd = date('d', $endTime); $edate_mm = date('m', $endTime); $edate_yy = date('Y', $endTime); $edate_hh = date('H', $endTime); $edate_ii = date('i', $endTime); ?> "> <input type="hidden" name="id" value="<?php echo $_GET['id']; ?>"> Event title: <br/> <input name="title" type="text" size="15" value="<?php echo $title; ?>"/><p/> Start date (dd/mm/yyyy): <br/> <input name="sdate_dd" type="text" size="2" value="<?php echo $sdate_dd; ?>" /> <input name="sdate_mm" type="text" size="2" value="<?php echo $sdate_mm; ?>"/> <input name="sdate_yy" type="text" size="4" value="<?php echo $sdate_yy; ?>"/><p/> Start time (hh:mm): <br/> <input name="sdate_hh" type="text" size="2" value="<?php echo $sdate_hh; ?>"/> <input name="sdate_ii" type="text" size="2" value="<?php echo $sdate_ii; ?>"/><br/> End date (dd/mm/yyyy): <br/> <input name="edate_dd" type="text" size="2" value="<?php echo $edate_dd; ?>" /> <input name="edate_mm" type="text" size="2" value="<?php echo $edate_mm; ?>" /> <input name="edate_yy" type="text" size="4" value="<?php echo $edate_yy; ?>" /><p/> End time (hh:mm): <br/> <input name="edate_hh" type="text" size="2" value="<?php echo $edate_hh; ?>" /> <input name="edate_ii" type="text" size="2" value="<?php echo $edate_ii; ?>" /><br/> <input name="submit" type="submit" value="Save" /> </form> <?php } else { // 폼을 제출했다면 // 입력한 정보를 검증 if (empty($_POST['id'])) { die('ERROR: Missing event ID'); } if (empty($_POST['title'])) { die('ERROR: Missing title'); } if (!checkdate($_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy'])) { die('ERROR: Invalid start date/time'); } if (!checkdate($_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy'])) { die('ERROR: Invalid end date/time'); } $title = htmlentities($_POST['title']); $start = date(DATE_ATOM, mktime($_POST['sdate_hh'], $_POST['sdate_ii'], 0, $_POST['sdate_mm'], $_POST['sdate_dd'], $_POST['sdate_yy'])); $end = date(DATE_ATOM, mktime($_POST['edate_hh'], $_POST['edate_ii'], 0, $_POST['edate_mm'], $_POST['edate_dd'], $_POST['edate_yy'])); // 존재하는 이벤트 레코드를 가져옴 // 이벤트 속성 갱신 // 변경 사항을 서버에 저장 try { $event = $gcal->getCalendarEventEntry('http://www.google.com/calendar/ feeds/default/private/full/' . $_POST['id']); $event->title = $gcal->newTitle($title); $when = $gcal->newWhen(); $when->startTime = $start; $when->endTime = $end; $event->when = array($when); $event->save(); } catch (Zend_Gdata_App_Exception $e) { die("Error: " . $e->getResponse()); } echo 'Event successfully modified!'; } ?> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>

Listing 9와 마찬가지로, Listing 10도 GET 메서드로 이벤트 식별자를 받는다. 그런 다음, getCalendarEventEntry() 메서드에 이벤트 식별자를 넘겨 해당 이벤트 정보를 가져온 후 세부 내역을 웹 폼으로 표시한다. 사용자는 폼에서 이벤트 정보를 살펴본 후 수정한다. 사용자가 수정한 폼을 제출하면 스크립트는 다시 구글 캘린더 데이터 API에 연결하고, 새 이벤트 정보가 담긴 <entry> 엘리먼트를 생성한 후, save() 메서드를 호출해 서버에 저장한다.

이벤트 검색하기

여느 구글 데이터 API와 마찬가지로, 캘린더 API 역시 REST 질의에 다음과 같은 몇 가지 매개변수를 지정하는 방법으로 개발자가 결과를 제어할 수 있다.

  • 매개변수 start-index: 항목에서 시작 색인을 지정한다.
  • 매개변수 max-results: 가져올 항목 수를 지정한다.
  • 매개변수 start-min과 start-max: 반환할 항목에 대한 날짜 범위를 지정한다.
  • 매개변수 orderby: 정렬 방법을 지정한다.


Listing 11은 Listing 2에서 가져왔던 이벤트 목록 전체 중 다음 주 이벤트만 시작 날짜 순서로 가져오는 코드다.


Listing 11: 날짜로 이벤트 검색하기

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Listing calendar contents</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <?php // 환경 설정 매개변수 설정 $userid = 'username%40googlemail.com'; $magicCookie = 'cook!e'; $start = urlencode(date(DATE_ATOM, strtotime('today 00:00'))); $end = urlencode(date(DATE_ATOM, strtotime('+7 days 23:59'))); // 피드 URL 생성 $feedURL = "http://www.google.com/calendar/feeds/$userid/private- $magicCookie/basic?start-min=$start&start-max=$end&orderby=starttime"; // 피드를 SimpleXML 객체로 읽음 $sxml = simplexml_load_file($feedURL); // 이벤트 개수 얻기 $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/'); $total = $counts->totalResults; ?> <h1><?php echo $sxml->title; ?></h1> <?php echo $total; ?> event(s) found. <p/> <ol> <?php // 범주에 속한 항목 순회 // 각 항목 세부 내역 출력 foreach ($sxml->entry as $entry) { $title = stripslashes($entry->title); $summary = stripslashes($entry->summary); echo "<li>\n"; echo "<h2>$title</h2>\n"; echo "$summary <br/>\n"; echo "</li>\n"; } ?> </ol> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>


또한 특정 질의 용어에 해당하는 항목만 반환하는 대신 전문 검색으로 캘린더를 검색한 후 해당 이벤트만 반환하는 방법도 있다. 구체적인 방법은 Listing 12를 참조한다.


Listing 12: 질의 용어로 이벤트 검색하기

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Listing calendar contents</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <?php // 환경 설정 매개변수 설정 $userid = 'username%40googlemail.com'; $magicCookie = 'cook!e'; $query = urlencode('party'); // 피드 URL 생성 $feedURL = "http://www.google.com/calendar/feeds/$userid/private- $magicCookie/basic?q=$query"; // 피드를 SimpleXML 객체로 읽음 $sxml = simplexml_load_file($feedURL); // 이벤트 개수 얻기 $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/'); $total = $counts->totalResults; ?> <h1><?php echo $sxml->title; ?></h1> <?php echo $total; ?> event(s) found. <p/> <ol> <?php // 범주에 속한 항목 순회 // 각 항목 세부 내역 출력 foreach ($sxml->entry as $entry) { $title = stripslashes($entry->title); $summary = stripslashes($entry->summary); echo "<li>\n"; echo "<h2>$title</h2>\n"; echo "$summary <br/>\n"; echo "</li>\n"; } ?> </ol> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>


젠드 클라이언트 라이브러리도 이런 매개변수를 지원한다. Listing 13은 Listing 8 코드에 검색 폼과 폼 처리기를 추가한 코드다.


Listing 13: 사용자와 대화식 검색 기능 추가

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <script type="text/javascript">if (!window.T) { window.T = {} } window.T.config = {"TOP_SSL_URL":"https://www.tistory.com","PREVIEW":false,"ROLE":"guest","PREV_PAGE":"","NEXT_PAGE":"","BLOG":{"id":973084,"name":"comphy","title":"졸리운 곰의 정보기술 여행 [김성준]","isDormancy":false,"nickName":"졸리운_곰","status":"open","profileStatus":"normal"},"NEED_COMMENT_LOGIN":false,"COMMENT_LOGIN_CONFIRM_MESSAGE":"","LOGIN_URL":"https://www.tistory.com/auth/login/?redirectUrl=https://comphy.tistory.com/category","DEFAULT_URL":"https://comphy.tistory.com","USER":{"name":null,"homepage":null,"id":0,"profileImage":null},"SUBSCRIPTION":{"status":"none","isConnected":false,"isPending":false,"isWait":false,"isProcessing":false,"isNone":true},"IS_LOGIN":false,"HAS_BLOG":false,"IS_SUPPORT":false,"IS_SCRAPABLE":false,"TOP_URL":"http://www.tistory.com","JOIN_URL":"https://www.tistory.com/member/join","PHASE":"prod","ROLE_GROUP":"visitor"}; window.T.entryInfo = null; window.appInfo = {"domain":"tistory.com","topUrl":"https://www.tistory.com","loginUrl":"https://www.tistory.com/auth/login","logoutUrl":"https://www.tistory.com/auth/logout"}; window.initData = {}; window.TistoryBlog = { basePath: "", url: "https://comphy.tistory.com", tistoryUrl: "https://comphy.tistory.com", manageUrl: "https://comphy.tistory.com/manage", token: "h3gkKXaF5YiE65IpGOhAO/X/cIjuapFq4DgwrC687r9Y+R8JKSjTf9xcYtwcPh8Z" }; var servicePath = ""; var blogURL = "";</script> <title>Listing calendar contents</title> <style type="text/css">.another_category { border: 1px solid #E5E5E5; padding: 10px 10px 5px; margin: 10px 0; clear: both; } .another_category h4 { font-size: 12px !important; margin: 0 !important; border-bottom: 1px solid #E5E5E5 !important; padding: 2px 0 6px !important; } .another_category h4 a { font-weight: bold !important; } .another_category table { table-layout: fixed; border-collapse: collapse; width: 100% !important; margin-top: 10px !important; } * html .another_category table { width: auto !important; } *:first-child + html .another_category table { width: auto !important; } .another_category th, .another_category td { padding: 0 0 4px !important; } .another_category th { text-align: left; font-size: 12px !important; font-weight: normal; word-break: break-all; overflow: hidden; line-height: 1.5; } .another_category td { text-align: right; width: 80px; font-size: 11px; } .another_category th a { font-weight: normal; text-decoration: none; border: none !important; } .another_category th a.current { font-weight: bold; text-decoration: none !important; border-bottom: 1px solid !important; } .another_category th span { font-weight: normal; text-decoration: none; font: 10px Tahoma, Sans-serif; border: none !important; } .another_category_color_gray, .another_category_color_gray h4 { border-color: #E5E5E5 !important; } .another_category_color_gray * { color: #909090 !important; } .another_category_color_gray th a.current { border-color: #909090 !important; } .another_category_color_gray h4, .another_category_color_gray h4 a { color: #737373 !important; } .another_category_color_red, .another_category_color_red h4 { border-color: #F6D4D3 !important; } .another_category_color_red * { color: #E86869 !important; } .another_category_color_red th a.current { border-color: #E86869 !important; } .another_category_color_red h4, .another_category_color_red h4 a { color: #ED0908 !important; } .another_category_color_green, .another_category_color_green h4 { border-color: #CCE7C8 !important; } .another_category_color_green * { color: #64C05B !important; } .another_category_color_green th a.current { border-color: #64C05B !important; } .another_category_color_green h4, .another_category_color_green h4 a { color: #3EA731 !important; } .another_category_color_blue, .another_category_color_blue h4 { border-color: #C8DAF2 !important; } .another_category_color_blue * { color: #477FD6 !important; } .another_category_color_blue th a.current { border-color: #477FD6 !important; } .another_category_color_blue h4, .another_category_color_blue h4 a { color: #1960CA !important; } .another_category_color_violet, .another_category_color_violet h4 { border-color: #E1CEEC !important; } .another_category_color_violet * { color: #9D64C5 !important; } .another_category_color_violet th a.current { border-color: #9D64C5 !important; } .another_category_color_violet h4, .another_category_color_violet h4 a { color: #7E2CB5 !important; } </style> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/revenue.css"/> <link rel="canonical" href="https://comphy.tistory.com"/> <!-- BEGIN STRUCTURED_DATA --> <script type="application/ld+json"> {"@context":"http://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":0,"item":{"@id":"https://comphy.tistory.com/544","name":"우분투(ubuntu) 서버 설정"}},{"@type":"ListItem","position":1,"item":{"@id":"https://comphy.tistory.com/545","name":"Object-Relational Mapping Strategies"}},{"@type":"ListItem","position":2,"item":{"@id":"https://comphy.tistory.com/546","name":"자료 정리"}},{"@type":"ListItem","position":3,"item":{"@id":"https://comphy.tistory.com/547","name":"전문가처럼 UNIX 명령행 편집하기"}},{"@type":"ListItem","position":4,"item":{"@id":"https://comphy.tistory.com/548","name":"PHP 프로그램에서 구글 캘린더 사용하기"}},{"@type":"ListItem","position":5,"item":{"@id":"https://comphy.tistory.com/549","name":"데이터베이스 성능 진단 및 통계"}},{"@type":"ListItem","position":6,"item":{"@id":"https://comphy.tistory.com/550","name":"대용량 데이터베이스 조회관련"}},{"@type":"ListItem","position":7,"item":{"@id":"https://comphy.tistory.com/551","name":"파이썬에서 한글쓰기"}},{"@type":"ListItem","position":8,"item":{"@id":"https://comphy.tistory.com/552","name":"파이썬으로 하는 웹 클라이언트 프로그래밍"}},{"@type":"ListItem","position":9,"item":{"@id":"https://comphy.tistory.com/553","name":"Web Spider 구현하기"}},{"@type":"ListItem","position":10,"item":{"@id":"https://comphy.tistory.com/554","name":"2008 디지털제어산업기사 자격증 사진"}},{"@type":"ListItem","position":11,"item":{"@id":"https://comphy.tistory.com/555","name":"2008 디지털제어산업기사 최종합격"}}]} </script> <!-- END STRUCTURED_DATA --> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/dialog.css"/> <link rel="stylesheet" type="text/css" href="//t1.daumcdn.net/tistory_admin/www/style/top/font.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/postBtn.css"/> <link rel="stylesheet" type="text/css" href="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/style/tistory.css"/> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> </head> <body> <?php require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar'); Zend_Loader::loadClass('Zend_Http_Client'); $gcal = Zend_Gdata_Calendar::AUTH_SERVICE_NAME; $user = "username@gmail.com"; $pass = "pass"; $client = Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $gcal); $gcal = new Zend_Gdata_Calendar($client); $query = $gcal->newEventQuery(); $query->setUser('default'); $query->setVisibility('private'); $query->setProjection('basic'); $query->setOrderby('starttime'); if(isset($_GET['q'])) { $query->setQuery($_GET['q']); } try { $feed = $gcal->getCalendarEventFeed($query); } catch (Zend_Gdata_App_Exception $e) { echo "Error: " . $e->getResponse(); } ?> <h1><?php echo $feed->title; ?></h1> <?php echo $feed->totalResults; ?> event(s) found. <p/> <ol> <?php foreach ($feed as $event) { echo "<li>\n"; echo "<h2>" . stripslashes($event->title) . "</h2>\n"; echo stripslashes($event->summary) . " <br/>\n"; $id = substr($event->id, strrpos($event->id, '/')+1); echo "<a href=\"edit.php?id=$id\">edit</a> | "; echo "<a href=\"delete.php?id=$id\">delete</a> <br/>\n"; echo "</li>\n"; } echo "</ul>"; ?> </ol> <p/> <a href="add.php">Add a new event</a><p/> " method="get"> Search for events containing:<br/> <input type="text" name="q" size="10"/><p/> <input type="submit" name="submit" value="Search"/> </form> <div style="margin:0; padding:0; border:none; background:none; float:none; clear:none; z-index:0"></div> <script type="text/javascript" src="https://tistory1.daumcdn.net/tistory_admin/userblog/userblog-381a16ca5dbe696f080b8a96eaf397c877057514/static/script/common.js"></script> <script type="text/javascript">window.roosevelt_params_queue = window.roosevelt_params_queue || [{channel_id: 'dk', channel_label: '{tistory}'}]</script> <script type="text/javascript" src="//t1.daumcdn.net/midas/rt/dk_bt/roosevelt_dk_bt.js" async="async"></script> <script>window.tiara = {"svcDomain":"user.tistory.com","section":"기타","trackPage":"글뷰_보기","page":"글뷰","key":"973084","customProps":{"userId":"0","blogId":"973084","entryId":"null","role":"guest","trackPage":"글뷰_보기","filterTarget":false},"entry":null,"kakaoAppKey":"3e6ddd834b023f24221217e370daed18","appUserId":"null"}</script> <script type="module" src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index.js"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/polyfills-legacy.js" nomodule="true" defer="true"></script> <script src="https://t1.daumcdn.net/tistory_admin/frontend/tiara/v1.0.5/index-legacy.js" nomodule="true" defer="true"></script> </body></html>

Listing 13을 실행한 결과는 그림 6과 같다.

그림 6. 검색 폼

그림 7은 'party'라는 낱말이 포함된 모든 이벤트를 검색한 결과다.


그림 7. 캘린더 검색 결과

요약

이상이다. 지금까지 PHP 프로그램에 구글 캘린더 서비스를 통합하는 방법을 살펴봤다. 구체적으로는 SimpleXML과 젠드 클라이언트 라이브러리를 사용하는 방법을 익혔다. 이 기사에서는 살펴본 내용은 다음과 같다.

  • 구글 캘린더 피드 형식 소개
  • 날짜와 키워드로 캘린더를 검색하는 방법
  • 캘린더 이벤트를 추가하고 수정하고 삭제하는 방법
  • 구글 캘린더를 바탕으로 나름대로 서비스를 구현하는 방법


위 예제에서 보듯이, 구글 캘린더 데이터 API는 완성도가 높고, 사용하기 편하며, 유연성도 높다. 그래서 개발자 입장에서 구글 캘린더를 바탕으로 자신만의 웹 앞단을 생성하기가 매우 쉽다. 직접 사용하면서 가능성을 짚어보기 바란다.

필자소개

Vikram Vaswani는 Melonfire 사의 창립자이자 CEO다. Melonefire 사는 오픈 소스 도구와 기술을 전문으로 다루는 컨설팅 서비스 회사다. Vikram Vaswani는 PHP Programming Solutions How to do Everything with PHP and MySQL 을 저술한 지은이이기도 하다.

'Computer Science' 카테고리의 다른 글

자료 정리  (0) 2008.11.30
전문가처럼 UNIX 명령행 편집하기  (0) 2008.11.30
데이터베이스 성능 진단 및 통계  (0) 2008.11.26
대용량 데이터베이스 조회관련  (0) 2008.11.26
Web Spider 구현하기  (0) 2008.11.24
2008/10/16 22:58
1. 튜닝 작업

- 성능 관리의 세가지 작업은 다음과 같다.


* 성능계획 : 어느정도의 데이터량이 있을때 어떤작업에서 어느정도의 성능이 필요한지 정확한 수치를 구하기 위해서 세우는 계획으로 하드웨어, 소프트웨어, 운영체제, 네트워크 infrastructure등과 같은 환경을 설정하는 과정이다.

* Instance 튜닝 : Instance는 메모리와 프로세스다. 어차피 작업은 메모리에서 일어나기 때문에 메모리와 작업을 수행하는 프로세스를 튜닝하는 것으로 오라클 데이터베이스 파라미터와 운영 체제 파라미터를 실제로 조정하는 것이다.

* SQL 튜닝 : SQL, PL/SQL문장을 최적으로 튜닝한다. (보통 문제의 80%는 SQL 문제다.)



2. 성능 계획

- 투자 옵션 : 어떤 작업에 어느정도의 시스템 리소스를 사용할것인지에 대한 옵션
- 시스템 구조
- Scalability(확장성)
- 응용 프로그램 설계 원칙
- 작업 로드 테스트, 모델링 및 구현
- 새 응용 프로그램 배치



3. Instance 튜닝

- 명확한 목표를 수립한다.
- 데이터베이스 구조에 메모리를 할당한다.
- 데이터베이스(파일을 의미 데이터파일, 컨트롤파일, 리두로그파일 등등)의 각 부분에서 I/O 요구 사항을 고려한다.
- 데이터베이스가 최적의 성능으로 실행되도록 운영 체제를 튜닝한다.

※ 튜닝 작업을 시작할 때는 구체적인 목표를 가지고 있어야 한다.
"작업 능률을 높여서 성능에 확연히 반영하자"와 같은 목포보다는
"분당 500개의 판매 트랜젝션 처리"와 같은 목표가 작업을 진행하기 한결 수월하다.



4. 성능 튜닝 방법론

- Instance를 튜닝하기 전에 OS 통계와 일반 시스템 상태를 확인하여 데이터베이스에 문제가 있는지 확인한다.

- 하향식으로 튜닝한다. 설계, 응용 프로그램, Instacne 순으로 튜닝한다.
O.S -> Contention (경합) -> I/O -> Memory -> Application -> Design 순서로 튜닝을 하는것이 가장 좋다.

- 예를 들어서, 디스크에서 테이블스페이스 레이아웃을 튜닝하게 전에 I/O경합을 유발하는 전체 테이블 스캔을 중지하지 않길 바란다.

- 잠재적 이점이 가장 뛰어난 영역을 튜닝한다. 이 과정에서 제시하는 튜닝 방법론은 간단하다.

- 가장 큰 병목 지점을 식별하고 이를 튜닝한다. 그리고 이 작업을 반복한다.

- 다양한 모든 튜닝 툴에는 가장 많은 시간을 소요하는 SQL문, 리소스 경합 또는 서비스를 식별할 수 있는 방법이 있다.

- 오라클 데이터베이스는 병목지점 식별 프로세스를 자동화하는 시간 모델과 metric을 제공한다.

- 목표가 충족되면 튜닝을 중지한다. 이를 위해서 튜닝의 목표를 정해야한다.
(오라클에 할당된 시스템 리소스는 한정되어 있기 때문에 사용할수 있는 리소스는 한정되어 있기 때문에 적절한 리소스 할당이 필요하기 때문에 적정전에서 튜닝을 중지한다.)



5. 통계 수집

- 성능 튜닝은 정확한 통계 수집이 관건이다.

- 통계 유형
* 옵티마이저 통계
* 시스템 통계

-통계 수집 유형
* 자동 : GATHER_STATS_JOB 사용
* 수동 : DBMS_STATS 패키지 사용
* 데이터베이스 초기화 파라미터 설정
* 다른 데이터베이스에서 통계 임포트

- 옵티마이저 통계는 데이터베이스와 데이터베이스의 객채에 대해 자세히 설명하는 데이터의 모음이다.

- Query 옵티마이저는 이러한 통계를 사용하여 각 SQL문에 가장 적합한 실행 계획을 선택한다.

- 시스템 통계는 I/O및 CPU성능과 활용률 같은 하드웨어 특성을 Query 옵티마이저에 설명한다.

- 실행 계획을 선택할 때 옵티마이저는 각 Query에 필요한 I/O및 CPU 리소스를 예측한다.

- Query 옵티마이저는 시스템 통계를 통해 I/O및 CPU 비용을 보다 정확하게 예측함으로써 보다 적합한 실행 계획을 선택할 수 있다.

- 시스템 통계는 DBMS_STATS.GATHER_SYSTEM_STATS 프로시저를 사용하여 수집한다.

- 오라클 데이터베이스는 시스템 통계를 수집할 때 지정된 기간 동안의 시스템 작업을 분석한다.

- 시스템 통계는 자동으로 수집되지 않는다.

- 오라클에서 시스템 통계 수집에 DBMS_STATS 패키지를 사용할 것을 권장한다.

- 권장되는 옵티마이저 통계 수집 방법은 오라클 데이터베이스를 통해 자동으로 통계를 수집하는 것이다.

- GATHER_STATS_JOB 작업은 데이터베이스를 생성할 때 자동으로 생성되어 스케줄러에 의해 관리된다.

- 이 작업은 누락되거나 오래된 옵티마이저 통계가 있는 데이터베이스의 모든 객체에 대해 통계를 수집한다.

- 수동으로 통계를 수집하려면 DBMS_STATS 패키지를 사용한다. 이 PL/SQL 패키지는 통계를 수정, 조회, 엑스포트, 임포트 및 삭제할 때도 사용할 수 있다.

- 또한 데이터베이스 초기화 파라미터를 통해 옵티마이저 및 시스템 통계 수집을 관리할 수 있다.

예)
* OPTIMIZER_DYNAMIC_SAMPLING 파라미터는 옵티마이저에서 수행하는 동적 샘플링의 레벨을 제어한다.

* STATISTICS_LEVEL 파라미터는 데이터베이스의 모든 주요 통계 수집 및 advisory를 제어하고 데이터베이스에 대한 통계 수집 레벨을 설정한다. 이 파라미터의 값은 BASIC, TYPICAL 및 ALL 이다.

* TIMED_STATISTICS 파라미터는 오라클 서버에 사용 가능한 대기 수 이외에 이벤트 대기 시간을 수집하도록 지시한다.

* STATISTICS_LEVEL 초기화 파라미터가 TYPICAL 또는 ALL로 설정된 경우에는 데이터베이스에 대한 시간별 시스템 통계가 자동으로 수집된다.

* STATISTICS_LEVEL이 BASIC로 설정된 경우에는 TIMED_STATISTICS를 TRUE로 설정해야 시간별 통계를 수집할 수 있다.

* STATISTICS_LEVEL을 BASIC로 설정하면 많은 자동화 기능이 비활성화되므로 권장하지 않는다.

- 초기화 파라미터 파일에서 또는 ALTER SYSTEM이나 ALTER SESSION 명령을 사용하여 TIMED_STATISTICS 또는 TIMED_OS_STATISTICS를 명시적으로 설정한 경우에는 명시적으로 설정한 값이 STATISTICS_LEVEL에서 파생된 값보다 우선한다.

- V$STATISTICS_LEVEL뷰를 Query하면 STATISTICAL_LEVEL 파라미터의 영향을 받는 파라미터를 확인할 수 있다.



6. Oracle 대기 이벤트

- 대기 이벤트 모음은
다양한 원인으로 대기해야 했거나 대기해야 할 세션 또는 프로세스에 대한 정보를 제공한다.

- 이러한 이벤트는 V$EVENT_NAME 뷰에 나열된다.




- 대기 이벤트는 서버 프로세스나 스레드에 의해 증가하는 통계로, 처리를 계속하려면 이벤트가 완료될 때까지 대기해야 함을 나타낸다.

- 대기 이벤트 데이터는 래치 경합, 버퍼 경합 및 I/O 경합처럼 성능에 영향을 줄 수 있는 다양한 증상의 문제를 보여준다.

- 오라클 데이터베이스에서는 free buffer wait(DBWR에서 더티 블록을 빨리 디스크에 쓰지 못해서 발생함), latch free(메모리에 락을 거는 상황), buffer busy waits, db file sequential read, db file scattered read를 비롯한 800개 이상의 대기 이벤트가 있다.

- EM을 사용하여 Performance 페이지를 열고 슬라이드에 표시된 "Sessions: Waiting and Working" 그래프에서 대기 이벤트를 확인할 수 있다.



7. 시스템 통계



※ V$ 로 시작하는 뷰 들은 Instance가 나서부터의 통계를 가지고 있다.
재부팅되면 날아간다..

- 성능 문제를 효율적으로 진단하려면 통계를 사용할 수 있어야 한다.

- 오라클 데이터베이스는 시스템, 세션 및 개별 SL문에 대한 다양한 유형의 누적 통계를 생성하며, 세그먼트와 서브스에 대한 누적 통계도 추적한다.

- 이러한 모든 범위에서 성능 문제를 분석하면 일반적으로 관심이 있는 기간 동안의 통계 변화(델타 값)를 확인할 수 있다.

- 대기 이벤트 통계
* 가능한 대기 이벤트는 모두 V$EVENT_NAME 뷰에 카탈로그화 된다.
* 모든 세션에 대한 누적 통계는 V$SYSTEM_EVENT에 저장된다.
* 이 뷰는 Instance 시작 이후 특정 이벤트에 대한 총 대기 수를 보여준다.
* 문제를 해결하려면 프로세스가 리소스를 기다렸는지 여부를 알아야 한다.


7-1. 시스템 자원의 통계

- 시스템 차원의 통계는 모두 V$STATNAME 뷰에 카탈로그화 됩니다. 오라클 10g에서는 약 330개의 통계를 제공한다.

- 서버는 계산된 모든 시스템 통계를 V$SYSTAT 뷰에 표시한다. 이 뷰를 Query하여 Instance 시작 이후의 누적 합계를 찾아볼 수 있다.

예)
SQL> SELECT name, class, value FROM v$sysstat; (실행해보기 바란다.)



7-2. SGA Global 통계

- 서버는 계산된 모든 메모리 통계를 V$SGASTAT 뷰에 표시한다. 이 뷰를 Query하여 Instance 시작 이후의 상세한 SGA 사용량에 대한 누적 합계를 찾아볼 수 있다.

예) SELECT * FROM v$sgastat;
POOLNAME BYTES
------------ --------------- --------
fixed_sga7780360
buffer_cache 251265824
log_buffer262144
shared pool sessions 1284644
shared pool sql area 22376876
......


- 위의 표시된 결과는 출력의 일부만 표시한 것이다.



8. 세션 관련 통계 표시



※ 세션 정보 : 로그인부터 로그아웃까지의 정보

- V$SESSION을 Query하여 로그온한 각 유저에 대한 현재 세션 정보를 표시할 수 있다.
* 예를 들어, V$SESSION을 사용하여 세션이 유저 세션을 나타내는지 데이터베이스 서버 프로세스(BACKGROUND)에 의해 생성 되어 었는지를 확인할 수 있다.

- 오라클 서버는 유저 세션 통계를 V$SESSTAT 뷰에 표시한다.
V$SESSION_EVENT 뷰는 세션별 이벤트 대기 정보를 나열한다.

- 동적 뷰의 누적 값은 데이터베이스 Instance가 종료될 때 재설정된다.

- V$MYSTAT 뷰는 현재 세션의 통계를 표시한다.

- V$SESSMETRIC 를 Query하여 모든 활성 세션의 성능 metric 값을 표시할 수도 있다.
* 이 뷰는 CPU 사용량, 물리적 읽기 수, 하드 구문 분석 수 및 놀리적 읽기 비율과 같은 성능 metric을 나열한다.



9. 문제 해결 및 튜닝 리뷰





10. 딕셔너리 뷰

- 다음 딕셔너리 및 특수 뷰는 DBMS_STATS 패키지를 사용하여 생성되는 유용한 통계를 제공한다.
* DBA_TABLES, DBA_TAB_COLUMNS
* DBA_CLUSTERS
* DBA_INDEXES
* DBA_TAB_HISTOGRMS

- 이 통계 정보는 DBMS_STATS의 해당 프로시저가 재실행될 때까지 변경되지 않는다.

- 데이터 저장영역을 자세히 보려면 DBMS_STATS패키지를 사용한다.

- 이 패키지는 통계를 수집하여 일부 DBA_XXX 뷰의 열을 채운다.

- DBMS_STATS는 다음과 관련된 뷰의 열을 채운다.
* Extent및 블록 내에 있는 테이블 데이터 저장 영역
> DBA_TABLES
> DBA_TAB_COLUMNS

* Extent 및 블록 내에 있는 클러스터 데이터 저장 영역
> DBA_CLUSTERS

* Extent 및 블록 내에 있는 인덱스 데이터 저장 영역과 인덱스 유용성
> DBA_INDEXES

- ANALYZE INDEX .... VALIDATE STRUCTURE 명령을 수행하면 인덱스에 대한 통계를 포함하는 INDEX_STATS 및 INDEX_HISTOGRAM 뷰가 채워진다.



11. 정지되었거나 매우 느린 데이터베이스에 대한 진단

- 데이터베이스가 너무 느리게 작동하거나 정지된 경우 문제 분석을 위해 다음 기능을 사용한다. 이 패키지는 통계를 수집하여 일부 DBA_XXX 뷰의 열을 채운다.

* 성능 모니터를 위해 SGA에 직접 액세스 (메모리 액세스 모드)
> V$SEESION
> V$SESSION_WAIT
> V$SYSTEM_EVENT
> V$SYSSTAT
> Enterprise Manager를 사용한 정지(Hang) 분석

대용량 데이터베이스 조회관련 질문

현재 순수 자바 응용프로그램을 짜고있는데요.. (JDK1.4.2)

조언이 좀 필요합니다.....

총 218개의 테이블중에 2개의 테이블 대상으로....

테이블 TB_RDFR에 FR_ID(PK), OG_NO, CT_YEAR, 외 80여개 컬럼이 있구요.

테이블 TB_RDRD에 RD_ID(PK), FR_ID 외 50여개의 컬럼이 있습니다.

테스트 DB에 TB_RDFR에는 약3,800개의 데이터가 들어있고요..

TB_RDRD에는 약 121,000개의 데이터가 들어있습니다.

(테스트DB이기에 실제DB에서는 훨씬 더 늘어날 가능성이 높습니다)

출력으로는

TB_RDFR 테이블에서.. 중복을 제외한 OG_NO 값,

TB_RDFR 테이블에서.. 중복을 제외한 OG_NO 각각의 값에 속한 중복을 제외한 CT_YEAR값,

TB_RDFR 테이블에서.. 위에 두 값(뽑아낸 OG_NO, 뽑아낸 CT_YEAR)이 동일한 FR_ID의 개수들의 합,

TB_RDRD테이블에서.. 위의 두 값(뽑아낸 OG_NO, 뽑아낸 CT_YEAR)이 동일한 각각의 FR_ID를 컬럼으로 가지는 RD_ID의 개수들의 합.

이렇게 4가지 항목이 한줄이 되어, txt파일로 출력이 되도록 하고있습니다.

현재 테스트해본 결과로는.. 173줄이 나오는데요..

저걸 3중 for문을 사용해서 그안에서 매번 DB에 connection을 하고 그 값을 가져다가 쓰는 방식으로 했더니..

DB 커넥션 3840회, 소요시간 519.485초 라는 어마어마한 결과가 나오네요... -0-

DB는 오라클9i사용하고요 로컬 DB는 아닙니다만.. 이쪽 컴은 나쁘지않은 수준입니다.. 팬터엄 2.5GHz 듀얼에 램2기가 노트북이니깐요..

문제가.. (DB커넥션으로 인한?) 소요시간이 너무 오래 걸린다는 점인데요...

그냥 말마따나.. 두개의 테이블의 데이터를 통채로 긁어다가 2차원 벡터에 넣어놓고 내부에서 돌려서 뽑아버리면

커넥션은 2번이면 끝날거 같긴한데.. 소요시간이 얼마나 줄어들지 모르겠고.. 메모리 잡아먹는게 감당이 될지 어떨지.. 아직은 상상뿐이지만.. (아직 그렇게는 안짰거든요.) 좀 두려워지네요..

대용량 DB 다뤄보신 경험있으신 고수 프로그래머님들께서..

저러한 경우 어떻게 해야 시스템 자원 적게 먹으면서 빠르게 처리할 수 있는지 강력한 조언좀 부탁드립니다.

참고로 제가 사용한 sql문은 밑에 있습니다.

sql = "select distinct OG_NO from TB_RDFR order by OG_NO";

sql = "select distinct CT_YEAR from TB_RDFR where OG_NO = '" + inputOgCd + "' order by CT_YEAR";

sql = "select FR_ID from TB_RDFR where OG_NO = '" + inputOgCd + "' and CT_YEAR = '" + inputCtYear + "' order by FR_ID";

sql = "select RD_ID from TB_RDRD where FR_ID = '" + inputFrId + "' order by RD_ID";

ps. RD_ID의 개수들의 합. <== 이거 뽑아낼때 count(RD_ID) 사용하면 안됩니다.. 원래 저 뒤에 하나 더 붙어 5가지 뽑아내야 하거든요.. RD_ID를 사용해서 또 비교해서요.. 거긴 아직 빼먹은 부분이라.. 하지만 해야하므로..

신고

의견 1

질문자 채택된 경우, 추가 답변 등록이 불가합니다.

re: 대용량 데이터베이스 조회관련 질문..!!!

질문자인사 1번은 한번 해봐야겠네요. 튜닝할때. 음. 4번은 저랑 상관없는.. 나중에.. 두번만에 통짜로 메모리에 올려놓고 처리했더니 전체조회 2번만에 프로그램이 뻗어버리네요.. 아웃 오브 메모리 익셉션. 일단 반반씩 부담을 가게 짜놓은 상태입니다.. 메모리 오버 에러도 확실히 안나는.. 근데 12시간 출력 테스트에 전체데이터의 반도 못뽑아냈..

안녕하세요

음 디비쪽을 좀 건드리면 될것 같내요

일단 쿼리를 보니 join을 전혀 안쓰셨는데 이유라도 있나요?

일단 join을 써서 필요한 데이터만 한방에 가져오는 방법이 있겠내요

퀴리를 잘 짜보시면 한방에도 가능 할것 같내요

두번째 방법은 인덱스를 확인해 보세요

where 절에 있는 애들은 인댁스가 있을수록 그 검색 결과는 빨라지니까 하지만 용량등등 고려 하시면서 인덱스를 만드세요

그리고 끝에 전부 ORDER BY 가 있는데 꼭 필요한거인가요?

결과를 모두 뽑아내서 합친다음 order by 하는게 더 빠를것 같내요

아무래도 정렬을 하게 되면 느리니까.

그리고 최후의 방법은 스케줄러를 사용하는 방법입니다.

저 정보를 실시간으로 보는것이 아니라면 하루에 한번 혹은 1시간에 한번씩 스케줄러를 돌려서

테이블에 필요한 데이터만 밀어 넣습니다.

그리고 프로그램단에서는 그테이블만 가져다가 보여주면 되겠죠

이정도면 해결 할수 있을것 같습니다.

파이썬에서 한글쓰기

2007/09/27 23:19 in Programming/Python

한글이 들어있는 파이썬 소스를 윈도우 커맨드 창(또은 리눅스 쉘)에서 그냥 실행하면 이런 에러가 뜹니다

E:\python>python int2base.py
File "int2base.py", line 11
SyntaxError: Non-ASCII character '\xc5' in file int2base.py on line 11, but no encoding declared; see
http://www.python.org/peps/pep-0263.html for details

이때는 파이썬 소스의 처음부분(1,2번째 줄) # -*- coding: cp949 -*- 을 추가하면 됩니다

추가로 유닉스 계열 운영체제를 사용할때는 #!/usr/bin/python를 첫줄에 붙이면 실행파일 속성을 주고 바로 파이썬코드를 실행할수 있습니다

ex)
#!/usr/bin/python
# -*- coding: cp949 -*-
print "
한글사용가능" #주석도 한글로 쓸수 있어요

파이썬으로 하는 웹 클라이언트 프로그래밍

저자: 데이브 워너(Dave Warner), 역 전순재

웹 클라이언트 프로그래밍은 웹에서 정보를 찾게 도와주는 강력한 테크닉이다. 웹 클라이언트는 (웹 주소 앞에 붙은 http) 하이퍼 텍스트 전송 프로토콜[1]을 사용하여 웹 서버로부터 데이터를 열람하는 프로그램 모두를 말한다. 웹 브라우저는 클라이언트이다. 웹 크롤러(crawler) 역시 클라이언트이다. 이 프로그램은 웹을 자동적으로 돌아다니면서 정보를 수집한다. 웹 클라이언트를 사용하면 웹에서 다른 사람들이 제공하는 서비스들을 이용할 수 있으며 웹 사이트에 역동적인 특징들을 추가할 수도 있다.

개발자들이 사용하는 툴박스에는 자연스럽게 웹 클라이언트 프로그래밍이 들어있다. 펄(Perl) 열성팬들은 이미 수년간 웹 클라이언트 프로그래밍을 이용해 왔다. 이런 웹 클라이언트 프로그래밍은 파이썬으로 처리하면 편리성과 유연성이 더욱 높은 수준에 이른다. 여기에 필요한 모든 기능들은 모듈 3개로 해결할 수 있다. HTTPLIB, URLLIB, 그리고 더 새로워진 XMLRPCLIB가 바로 그것들이다. 진정한 파이썬 스타일로, 각 모듈은 기존의 모듈 위에 구축되어 애플리케이션에 견고하면서도 잘 디자인된 기반을 제공한다. XMLRPCLIB는 다음에 논하기로 하고 본 기사에서는 첫 번째 모듈 두 개에 대해 다루겠다.

우리가 볼 예제에서는 미어캣(Meerkat)을 사용하겠다. 이럴 경우 여러분이 필자와 같은 생각을 가지고 있다면 시간을 들여 오픈 소스 공동체의 동향과 개발 상황들을 추적해서 경쟁력을 확보할 것이다. 미어캣(Meerkat)은 이 작업을 훨씬 더 쉽게 만들어주는 도구이다. 미어켓은 오픈 와이어 서비스(open wire service)로서 오픈 소스 컴퓨팅과 관련된 방대한 양의 정보를 수집하고 정리한다. 미어캣의 브라우저 인터페이스는 유연하고 맞춤가능하지만, 웹 클라이언트 프로그래밍을 사용하면 우리는 이 정보를 훓어보고, 추출하는 것은 물론이고 나중에 사용하기 위해 오프 라인에 저장할 수도 있다. 우리는 먼저 HTTPLIB를 상호대화적으로 사용하여 미어켓에 접근할 것이다. 그리고 나서 URLLIB를 통해 미어켓의 개방 API(Meerkat's Open API)에 접근해 들어가 맞춤가능한 정보 수집도구를 만들어 볼 것이다.

HTTPLIB

HTTPLIB는 소켓(socket) 모듈을 살짝 감싼 포장자(wrapper)이다. 앞에서 언급한 3개의 라이브러리 중에서 웹 사이트에 접근할 때 가장 제어가 쉬운 모듈이 HTTPLIB이다. 그렇지만 과업을 달성하기 위해서는 추가 작업을 더 해야만 제대로 제어할 수 있다. http 통신규약(protocol)은 "정보를 저장하지 않기(stateless)" 때문이다. 따라서 이전의 요구는 전혀 기억하지 않는다. 각 요구에 대해 여러분은 HTTPLIB 객체를 새롭게 구성하여 웹 사이트에 접속해야 한다. 요구들은 웹 서버와 대화를 형성하고 웹 브라우저를 흉내낸다. 라엘 돈페스트(Rael Dornfest)의 개방 API를 사용해서 미어켓에 접속해 보자. 그리고 어떤 결과를 얻는지 살펴 보자. 대화는 일련의 서술문들을 구축함으로써 시작된다. 먼저 원하는 작업이 무엇인지 서술한다. 그리고 나서 웹 서버에게 여러분을 식별시킨다.
>>> import httplib>>> host = 'www.oreillynet.com'>>> h = httplib.HTTP(host)>>> h.putrequest('GET', '/meerkat/?_fl=minimal')>>> h.putheader('Host', host)>>> h.putheader('User-agent', 'python-httplib')>>> h.endheaders()>>>
GET 요청은 어느 페이지를 받기 원하는지 서버에게 전달한다. 호스트 헤더(Host header)는 질의하고자 하는 도메인 이름을 서버에게 전달한다. 현대적인 서버들은 HTTP 1.1을 사용하여 여러 도메인을 같은 주소에서 사용할 수 있다. 만약 서버에게 어떤 도메인 이름을 원하는지 알려주지 않는다면, 여러분은 '302' 출력전환(redirection) 응답을 반환 코드로 얻게 될 것이다. 사용자-에이전트 헤더(User-agent header)는 서버에게 여러분이 어떤 종류의 클라이언트인지 알려 준다. 그래야만 서버는 여러분에게 보낼 수 있는 것과 없는 것이 무엇인지를 이해할 수 있기 때문이다. 이것이 웹 서버가 요구를 처리하기 위해 필요한 정보이다. 다음으로 여러분은 응답을 요구한다.
>>> returncode, returnmsg, headers = h.getreply()>>> if returncode == 200:  #OK...         f = h.getfile()...         print f.read()...
이렇게 하면 현재의 미어켓 페이지를 간략한 형태(minimal flavor)로 출력할 것이다. 응답 머리부와 응답 내용은 개별적으로 반환되며, 이렇게 하면 반환된 데이터의 문제를 해결하거나 해석하는데 모두 도움이 된다. 만약 응답 머리부를 보고 싶다면, print headers를 사용하면 된다.

HTTPLIB 모듈은 소켓 프로그래밍의 기계적인 면을 구별해준다. 게다가 HTTPLIB 모듈은 버퍼링을 위해 파일 객체를 사용하기 때문에 친숙하게 데이터 조작에 접근할 수 있지만 더욱 강력한 웹 클라이언트 애플리케이션을 위한 빌딩 블록이나 문제가 생긴 웹사이트와 상호 대화를 나누기 위한 빌딩 블록으로 더 잘 맞는다. HTTPLIB 모듈이 가지는 유용한 디버그 능력은 두 영역 모두에 도움을 준다. 객체 초기화 후에 어느 곳에서나
h.set_debuglevel(1) 메소드를 호출하면 HTTPLIB에 접근할 수 있다. (예제에서는 다음의 h = httplib.HTTP(host) 라인이다). 디버그 수준이 1에 설정되어 있으면 HTTPLIB 모듈은 getreply()을 호출한 결과들과 요청들을 화면에 응답할 것이다.

파이썬의 상호대화적인 특성 덕분에 즐겁게 HTTPLIB를 사용하여 웹 사이트를 분석할 수 있다. 이 모듈을 익히면 웹 사이트의 문제점들을 진단하기 위한 강력하고 유연한 도구를 가지게 되는 것이다. 또 시간을 가지고 HTTPLIB에 대한 소스를 살펴보라. 200줄도 안되는 코드임에도 불구하고, HTTPLIB를 사용하면 빠르고 쉽게 파이썬으로 소켓 프로그래밍을 시작할 수 있다.

URLLIB

URLLIB는 HTTPLIB에서 발견되는 기능에 대해 세련된 인터페이스를 제공한다. URLLIB 모듈은 웹 사이트를 분석하는 것보다는 데이터 그 자체를 찾아 내는데 가장 유용하게 사용된다. 다음 코드는 URLLIB를 사용해서 위와 똑같은 상호작용을 한다. (주의: 마지막 줄을 화면 출력을 위해 두 줄로 쪼개었지만, 여러분의 스크립트에서는 나누지 말 것!)
>>> import urllib>>> u = urllib.urlopen('http://www.oreillynet.com/meerkat/?_fl=minimal')
이것이 다이다! 한 줄로 미어켓(Meerkat)에 접근해서 데이터를 얻었으며, 그 데이터를 임시 저장소에 보관했다. 해더 정보에 접근하려면
>>> print u.headers
그리고 전체 파일을 보려면
>>>print u.read()
그러나 이것이 전부는 아니다. URLLIB는 HTTP뿐만 아니라 FTP, Gopher, 심지어는 같은 방식으로 지역 파일에도 접근할 수 있다. 이 모듈이 제공하는 많은 유틸리티 기능에는 url 해석하기, 문자열을 url-안전 형태로 코드전환(encode)하기, 그리고 한참 긴 데이터 전송 중에 진행 표시를 제공하기가 있다.

미어켓을 사용하는 예제 하나

한 그룹의 고객(client)들이 있는데 그들이 최신 리눅스 소식을 이메일로 꾸준히 받아보기를 바라고 있다고 상상해보자. 우리는 짧은 스크립트를 작성할 수 있다. URLLIB를 사용하여 이 정보를 Meerkat으로부터 얻는다. 링크의 목록을 구축한다. 그리고 그 링크들을 나중에 전송하기 위해 파일에 저장한다. 미어켓(Meerkat)의 저자인 라엘 돈페스트(
Rael Dornfest)는 미어켓 API를 통해 우리 대신 대부분의 작업을 이미 완성해 놓았다. 남아있는 것은 요구를 구성하고, 링크를 해석하며, 나중에 전송하기 위해 그 결과를 저장하는 것 뿐이다.

단지 이것 때문에 사람들이 미어캣으로 전향하는 것일까? 이러한 "정보받기(passive)" 서비스를 제공하면 사람들은 그 정보를 한가할 때 볼 수 있다. 그리고 그 정보를 골라서 친숙한 형식(예를 들어 이메일)으로 저장할 수 있다. 월요일 아침에 메일함에서 뉴스들이 도착하기를 기다리기만 하면, 한 주간 "말려 올라간" 정보들을 하나도 놓치지 않을 것이다.

미어캣의 간략한 형식(minimal flavor)은 기사가 15개로 제한되므로 데이터를 놓칠 가능성을 줄이기 위해 우리는 스크립트를 (즉, Unix의 cron 작업 또는 NT의 AT 명령어를 사용하여) 매 시간 실행시킬 것이다. 여기에 우리가 사용할 url이 있다 (주의: 우리는 이 줄을 두 개의 줄로 나누어 화면에 표시했다. 이 URL을 사용한 결과는
여기에서 볼 수 있다).
http://www.oreillynet.com/meerkat/?p=5&t=1HOUR&_fl=minimal&_de=0&_ca=0&_ch=0&_da=0
이 코드는 지난 한 시간 동안에 있었던 모든 리눅스 이야기들(profile=5)을 끌어 와서, 데이터를 간략한 형식(minimal flavor)으로 보여준다. 설명도 없고, 범주정보도 없으며, 채널 정보, 데이터 정보도 없다. 우리는 또한 정규 표현식 모듈의 도움을 받아 링크 정보를 추출하고 출력결과를 추가 모드로 열려진 파일 객체로 방향전환할 것이다.

결론

우리는 겨우 이 모듈들의 표면만을 건드려 보았다. 웹 클라이언트 작업에 사용할 수 있는 것 말고도 파이썬에는 다른 많은 네트워크 프로그래밍 모듈을 사용할 수 있다. 웹 클라이언트 프로그래밍은 특히 방대한 양의 계산표형 테이터를 다룰 때 유용하게 사용할 수 있다. 최근의 한 전자 데이터 교환(EDI) 프로젝트에서 우리는 웹 클라이언트 프로그래밍을 사용하여 거추장스러운 독점 소프트웨어 패키지를 우회하였다. 갱신된 가격 정보를 웹으로부터 직접 얻어서 데이터베이스에 집어 넣었다. 그렇게 함으로써 우리는 많은 시간을 절약하고 좌절감을 극복할 수 있었다.

웹 클라이언트 프로그래밍은 웹 사이트의 구조와 견고성을 테스트하는 데에도 유용하게 사용될 수 있다. 일반적으로는 죽은 링크들을 점검하는 방법으로 사용된다. 표준 파이썬 배포본에는 이것에 대한 완전한 예제가 딸려온다. 이 예제는 URLLIB에 기초한다. Tk-기반의 프론트 엔드
[2] 모듈인 웹체커(Webchecker)는 파이썬 배포본의 tools 하부디렉토리 아래에서 찾아볼 수 있다. 또다른 파이썬 도구인 린봇(Linbot)은 URLLIB 모듈의 기능을 개선해 준다. 린봇으로 여러분은 웹 사이트의 문제를 모두 해결할 수 있다. 웹 사이트들이 점점 더 복잡해짐에 따라 웹 사이트의 질을 확인하기 위해서는 다른 웹 클라이언트 애플리케이션들이 필요하게 될 것이다.

웹 클라이언트 프로그래밍에는 함정이 하나 있다. 여러분의 프로그램은 페이지의 형식이 조금만 변경되어도 영향을 받는다. 반드시 웹 사이트가 오늘 데이터를 출력하는 방식이 내일도 그대로 유지된다고 장담할 수는 없다. 페이지의 형식이 바뀌면 프로그램도 바뀌어야 한다. 사람들이 XML에 그렇게 흥분하는 이유 중 하나가 바로 이것 때문이다. 웹에서 데이터에 태그를 붙여 의미를 주면 형식은 중요하지 않게 된다. XML 표준이 진화하고 범세계적으로 인정됨에 따라, 훨씬 더 쉽게 그리고 튼튼하게 XML 데이터를 처리하게 될 것이다.

우리가 여기에서 다룬 도구들에는 약간의 제한이 있다. HTTPLIB 모듈과 URLLIB모듈은 클라이언트-기반 작업에는 탁월하지만 오직 한 번에 한 개의 요청만을 처리할 수 있기 때문에 서버를 구축하는데 사용해서는 안된다. 비동기적인 처리방법을 제공하기 위해 샘 러싱(Sam Rushing)은 멋진 도구모음을 구축하였다. 이 도구모음은 asyncore.py 를 포함하여 표준 파이썬 배포본에 딸려 온다. 이 접근법을 사용하는 가장 강력한 예제는 조프(
ZOPE)이다. 조프는 애플리케이션 서버로서 샘 러싱(Sam Rushing)의 메듀사 엔진(Medusa engine)을 사용하여 구축한 빠른 http 서버를 포함하고 있다.

다음 기사에서는 XML과 웹 클라이언트 프로그래밍을 어떻게 XMLRPCLIB 모듈에 결합하는지에 대해 논의해볼 생각이다. XML을 사용하면 미어켓(Meerkat) API로부터 더욱 많은 기능을 짜낼 수 있다.
각주
[1] Hyper Text Transfer Protocol
[2] front end: 프론트 엔드
예) GUI는 front end 이며 구현된 기능들은 back end이다.

데이브 워너(Dave Warner)는 Federal Data Corporation사의 선임 프로그래머이자 데이터베이스 관리자(DBA)이다. 그는 P자로 시작하는 언어(Python, Perl, PowerBuilder)로 관계형 데이터베이스에 접근하는 방법을 연구하고 있다.

Copyright © 2008 Hanbit Media, Inc.

리눅스에서 웹 스파이더(Web spider) 구현하기 (한글)

간단한 스파이더와 스크래퍼로 인터넷 콘텐트 모으기

developerWorks
문서 옵션
수평출력으로 설정

이 페이지 출력

이 페이지를 이메일로 보내기

이 페이지를 이메일로 보내기


제안 및 의견
피드백

난이도 : 중급

M. Tim Jones, Consultant Engineer, Emulex

2007 년 4 월 17 일

웹 스파이더(Web spider)는 인터넷을 크롤링 하며 정보를 수집하고, 필터링 하며, 사용자를 위한 정보를 한데 모으는 소프트웨어 에이전트입니다. 일반 스크립팅 언어와 웹 모듈을 사용하면 웹 스파이더를 쉽게 구현할 수 있습니다. 이 글에서는 리눅스?玲? 스파이더와 스크래퍼를 구현하여 웹 사이트를 크롤링 하며 정보를 모으는 방법을 설명합니다.

스파이더(spider)는 특정 목적을 위해 특정한 방법으로 인터넷을 크롤링(crawl) 하는 프로그램이다. 이 프로그램의 목적은 정보를 수집하거나 웹 사이트의 구조와 유효성을 파악하는 것이다. 스파이더는 Google과 AltaVista 같은 현대적인 검색 엔진의 기초가 된다. 이러한 스파이더들은 웹에서 자동으로 데이터를 검색하여, 검색어에 가장 잘 맞는 웹 사이트의 내용을 인덱싱 하는 다른 애플리케이션에 전달한다.

에이전트로서의 웹 스파이더

웹 스파이더와 스크래퍼는 소프트웨어 로봇 또는 에이전트(1980년대 초, Alan Kay에 의해 만들어진 단어)의 또 다른 형태이다. Alan이 만든 에이전트라는 개념은 컴퓨터 세계에서의 사용자용 프록시였다. 이 에이전트에는 목표와, 그 목표에 대한 작업이 주어질 수 있었다. 이것이 어떤 한계에 도달하면 사용자에게서 어드바이스를 요청할 수 있고 계속적으로 그 목표를 수행할 수 있었다.

오늘날 에이전트는 자율성(autonomy), 적응성(adaptiveness), 통신, 다른 에이전트와의 협업(collaboration) 같은 애트리뷰트들로 구분된다. 에이전트 이동성(mobility)과 개성(personality) 같은 기타 애트리뷰트들은 오늘날 에이전트 연구의 목표이다. 이 글에서 설명하는 웹 스파이더는 에이전트 분류법에서 Task-Specific Agents로 분류된다.

스파이더와 비슷한 것으로 웹 스크래퍼(Web scraper)가 있다. 스크래퍼는 스파이더의 한 유형으로서, 웹에서 제품이나 서비스 비용 같은 특수한 내용이 스크래핑 대상이 된다. 한 가지 사용 예제로는 가격 비교가 있는데, 해당 제품의 가격을 파악하여 본인 제품의 가격을 조정하고, 이에 따라 광고를 하는 것이다. 스크래퍼는 많은 웹 소스들에서 데이터를 모으고 그 정보를 사용자에게 제공한다.

생물학적인 동기

스파이더의 본질을 생각할 때, 고립성이 아닌 환경과의 인터랙션에 맞추어 이를 생각하게 된다. 스파이더는 자신의 길을 보고 감지하며, 한 장소에서 또 다른 장소로 의미 있는 방식으로 이동한다. 웹 스파이더도 비슷한 방식으로 작동한다. 웹 스파이더는 고급 언어로 작성된 프로그램이며, Hypertext Transfer Protocol (HTTP) 같은 네트워킹 프로토콜을 사용하여 환경과 인터랙팅 한다. 스파이더가 여러분과 통신하기 원한다면, Simple Mail Transfer Protocol (SMTP)을 사용하여 이메일 메시지를 보낼 수 있다.

스파이더는 HTTP 또는 SMTP 로 국한되지 않는다. 일부 스파이더는 SOAP 또는 Extensible Markup Language Remote Procedure Call (XML-RPC) 프로토콜 같은 웹 서비스를 사용한다. 다른 스파이더는 Network News Transfer Protocol (NNTP)을 통해 뉴스 그룹과 소통하거나, Really Simple Syndication (RSS) 피드로 흥미로운 뉴스 아이템들을 찾는다. 대부분의 스파이더는 본질적으로 명암 강도(light-dark intensity)와 움직임의 변화만 볼 수 있지만, 웹 스파이더들은 많은 유형의 프로토콜들을 사용하여 보고 감지할 수 있다.




위로


스파이더와 스크래퍼의 애플리케이션

스파이더의 눈과 다리

웹 스파이더가 인터넷을 보고 움직이는 주요 수단은 HTTP이며, HTTP는 메시지 중심 프로토콜로서, 이곳에서 클라이언트는 서버와 연결되어 해당 요청을 수행하며, 서버는 응답을 제공한다. 각각의 요청과 응답은 헤더와 바디로 구성되고, 헤더는 상태 정보와 바디의 내용에 대한 디스크립션을 제공한다.

HTTP는 세 가지 기본적인 유형의 요청을 제공한다. 첫 번째가 HEAD인데, 이것은 서버에 있는 자산에 대한 정보를 요청한다. 두 번째는 GET으로서 파일 또는 이미지 같은 자산을 요청한다. 마지막으로, POST 요청은 클라이언트가 웹 페이지를 통해(일반적으로 웹 폼을 통해) 서버와 인터랙팅 할 수 있다.

웹 스파이더와 스크래퍼는 유용한 애플리케이션이고, 따라서 좋든 나쁘든, 여러 가지 다양한 유형의 사용법이 있다. 이러한 기술을 사용하는 몇 가지 애플리케이션에 대해 살펴보도록 하자.

검색 엔진 웹 크롤러(crawler)

웹 스파이더는 인터넷 검색을 쉽고 효율적으로 만든다. 검색 엔진은 많은 웹 스파이더들을 사용하여 인터넷 상의 웹 페이지들을 크롤링 하고, 콘텐트를 리턴하며, 이를 인덱싱 한다. 이것이 완료되면, 검색 엔진은 로컬 인덱스를 빠르게 검색하여 검색에 맞는 가장 합당한 결과를 찾는다. Google은 PageRank 알고리즘을 사용하는데, 검색 결과의 웹 페이지 랭크(rank)는 얼마나 많은 페이지들이 여기에 링크되어 있는지를 나타내는 것이다. 이것은 투표(vote)로서도 작동하는데, 높은 투표를 가진 페이지들은 가장 높은 랭크를 얻는다.

이와 같이 인터넷을 검색하는 것은 웹 콘텐트와 인덱서를 통신하는데 있어서 대역폭과 결과를 인덱싱 하는 전산 비용 관점에서 볼 때 비용이 많이 든다. 많은 스토리지가 이와 같은 것을 필요로 하지만, Google이 Gmail 사용자들에게 1,000 메가바이트의 스토리지를 제공한다고 생각한다면 이것은 문제도 아니다.

웹 스파이더는 일련의 정책을 사용하여 인터넷 상의 흐름을 최소화 한다. Google은 80억 개 이상의 웹 페이지들을 인덱싱 한다. 실행 정책은 크롤러가 인덱서로 어떤 페이지들을 가져오는지, 웹 사이트로 가서 이를 다시 체크하는 빈도수는 어느 정도인지에 대한 politeness 정책을 정의한다. 웹 서버는 robot.txt라고 하는 파일을 사용하여 크롤러를 차단할 수 있다.

기업용 웹 크롤러

표준 검색 엔진 스파이더와 마찬가지로, 기업용 웹 스파이더는 일반인이 사용할 수 없는 콘텐트를 인덱싱 한다. 예를 들어, 기업들은 사원들이 사용하는 내부 웹 사이트를 갖고 있다. 이러한 유형의 스파이더는 로컬 환경으로 제한된다. 검색이 제한되기 때문에 더 많은 전산 파워가 사용되며, 전문화 되고 보다 완벽한 인덱스가 가능하다. Google은 한 단계 더 나아가서 데스크탑 검색 엔진을 제공하여 여러분 개인용 컴퓨터의 콘텐트를 인덱싱 한다.

전문화된 크롤러

콘텐트를 압축하거나 통계를 만들어 내는 등, 특수한 크롤러도 있다. 압축 크롤러는 웹 사이트를 크롤링 하면서, 콘텐트를 로컬로 가져와서 장기적인 저장 미디어에 저장되도록 한다. 이것은 백업용으로 사용될 수 있고, 더 크게는 인터넷 콘텐트의 스냅샷을 만들기도 한다. 통계는 인터넷 콘텐트와 무엇이 부족한지를 이해하는데 도움이 된다. 크롤러는 얼마나 많은 웹 서버들이 실행되는지, 특정 유형의 웹 서버들이 얼마나 많은지, 사용할 수 있는 웹 페이지 수, 깨진 링크의 수(HTTP 404 error, page not found 등을 리턴함) 등을 규명하는데 사용된다.

기타 전문적인 크롤러에는 웹 사이트 체커(checker)도 있다. 이 크롤러는 소실된 콘텐트를 찾고, 모든 링크들을 검사하며, 여러분의 Hypertext Markup Language (HTML)이 유효한지를 확인한다.

이메일을 모으는 크롤러

이제 어두운 쪽으로 가보도록 하자. 불행하게도, 일부 썩은 사과들이 인터넷을 망치고 있다. 이메일을 모으는 크롤러들은 이메일 주소가 있는 웹 사이트를 검색하여 대량의 스팸을 생성하는데 사용한다. 포스티니(Postini) 보고서(2005년 8월)에 따르면, 포스티니(Postini) 사용자들의 모든 이메일 메시지들의 70%가 스팸 이라고 한다.

이메일 모으기는 가장 흔한 크롤러 동작 메커니즘 중 하나이다. 이 글에서는 이 마지막 크롤러 예제를 설명한다.

지금까지, 웹 스파이더와 스크래퍼를 설명했다. 다음 네 가지 예제들은 Ruby와 Python 같은 현대적인 스크립팅 언어를 사용하여 리눅스용 스파이더와 스크래퍼를 구현하는 방법을 설명하겠다.




위로


예제 1: 일반 스크래퍼

이 예제를 통해 주어진 웹 사이트에 대해 어떤 종류의 웹 서버가 실행되는지를 규명하는 방법을 설명하겠다. 이것은 매우 재미있고, 정부, 학계, 업계에서 어떤 종류의 웹 서버를 사용하는지도 알 수 있다.

Listing 1은 HTTP 서버를 규명하기 위해 웹 사이트를 스크래핑 하는 Ruby 스크립트이다. Net::HTTP 클래스는 HTTP 클라이언트와 GET, HEAD, POST HTTP 메소드를 실행한다. HTTP 서버에 요청을 보낼 때 마다, HTTP 메시지 응답의 일부에서는 콘텐트가 제공되는 서버를 나타낸다. 그 사이트에서 페이지를 다운로드 하기 보다는, HEAD 메소드를 사용하여 루트 페이지('/')에 대한 정보를 얻는다. HTTP 서버가 성공적인 응답을 보내는 한("200" 응답 코드로 나타남), 응답의 각 라인을 반복하면서 server 키를 검색하고, 이것을 찾으면 값을 프린트 한다. 이 키의 값은 HTTP 서버를 나타내는 스트링이다.


Listing 1. 간단한 메타데이터 스크래핑을 위한 Ruby 스크립트(srvinfo.rb)
                #!/usr/local/bin/rubyrequire 'net/http'# Get the first argument from the command-line (the URL)url = ARGV[0]begin  # Create a new HTTP connection  httpCon = Net::HTTP.new( url, 80 )  # Perform a HEAD request  resp, data = httpCon.head( "/", nil )  # If it succeeded (200 is success)  if resp.code == "200" then    # Iterate through the response hash    resp.each {|key,val|      # If the key is the server, print the value      if key == "server" then        print "  The server at "+url+" is "+val+"\n"      end    }  endend

srvinfo 스크립트를 사용하는 방법을 설명하는 것 외에도, Listing 2는 많은 정부, 학계, 비즈니스 웹 사이트에서 가져온 결과들도 보여준다. Apache (68%)부터 Sun과 Microsoft?? Internet Information Services (IIS)까지 다양하다. 서버가 리포팅 되지 않은 경우도 있다. 미크로네시아(Federated States of Micronesi)는 구 버전의 Apache를 실행하고 있고(이제 업데이트가 필요하다.), Apache.org는 첨단을 달리고 있다는 사실이 흥미롭다.


Listing 2. 서버 스크래퍼의 사용 예제
                [mtj@camus]$ ./srvrinfo.rb www.whitehouse.gov  The server at www.whitehouse.gov is Apache[mtj@camus]$ ./srvrinfo.rb www.cisco.com  The server at www.cisco.com is Apache/2.0 (Unix)[mtj@camus]$ ./srvrinfo.rb www.gov.ru  The server at www.gov.ru is Apache/1.3.29 (Unix)[mtj@camus]$ ./srvrinfo.rb www.gov.cn[mtj@camus]$ ./srvrinfo.rb www.kantei.go.jp  The server at www.kantei.go.jp is Apache[mtj@camus]$ ./srvrinfo.rb www.pmo.gov.to  The server at www.pmo.gov.to is Apache/2.0.46 (Red Hat Linux)[mtj@camus]$ ./srvrinfo.rb www.mozambique.mz  The server at www.mozambique.mz is Apache/1.3.27    (Unix) PHP/3.0.18 PHP/4.2.3[mtj@camus]$ ./srvrinfo.rb www.cisco.com  The server at www.cisco.com is Apache/1.0 (Unix)[mtj@camus]$ ./srvrinfo.rb www.mit.edu  The server at www.mit.edu is MIT Web Server Apache/1.3.26 Mark/1.5 	(Unix) mod_ssl/2.8.9 OpenSSL/0.9.7c[mtj@camus]$ ./srvrinfo.rb www.stanford.edu  The server at www.stanford.edu is Apache/2.0.54 (Debian GNU/Linux) 	mod_fastcgi/2.4.2 mod_ssl/2.0.54 OpenSSL/0.9.7e WebAuth/3.2.8[mtj@camus]$ ./srvrinfo.rb www.fsmgov.org  The server at www.fsmgov.org is Apache/1.3.27 (Unix) PHP/4.3.1[mtj@camus]$ ./srvrinfo.rb www.csuchico.edu  The server at www.csuchico.edu is Sun-ONE-Web-Server/6.1[mtj@camus]$ ./srvrinfo.rb www.sun.com  The server at www.sun.com is Sun Java System Web Server 6.1[mtj@camus]$ ./srvrinfo.rb www.microsoft.com  The server at www.microsoft.com is Microsoft-IIS/6.0[mtj@camus]$ ./srvrinfo.rb www.apache.orgThe server at www.apache.org is Apache/2.2.3 (Unix) 	mod_ssl/2.2.3 OpenSSL/0.9.7g

이것은 유용한 데이터이고, 정부와 학교들이 자신들의 웹 서버로 무엇을 사용하는지를 알 수 있어서 재미있다. 다음 예제에서는 보다 덜 유용한 주식 시세 스크래퍼를 설명하겠다.




위로


예제 2: 주식 시세 스크래퍼

이 예제에서는, 간단한 웹 스크래퍼(스크린 스크래퍼(screen scraper))를 구현하여 주식 시세 정보를 모으도록 하겠다. 다음과 같이 응답 웹 페이지에 한 패턴을 활용하는 방식을 사용할 것이다.


Listing 3. 주식 시세용 웹 스크래퍼
                #!/usr/local/bin/rubyrequire 'net/http'host = "www.smartmoney.com"link = "/eqsnaps/index.cfm?story=snapshot&symbol="+ARGV[0]begin  # Create a new HTTP connection  httpCon = Net::HTTP.new( host, 80 )  # Perform a HEAD request  resp = httpCon.get( link, nil )  stroffset = resp.body =~ /class="price">/  subset = resp.body.slice(stroffset+14, 10)  limit = subset.index('<')  print ARGV[0] + " current stock price " + subset[0..limit-1] +          " (from stockmoney.com)\n"end

이 Ruby 스크립트에서, HTTP 클라이언트를 서버로 연결하고(이 경우, www.smartmoney.com), (&symbol=<symbol>을 통해) 사용자에 의해 전달된 것처럼 주식 시세를 요청하는 링크를 구현한다. 나는 HTTP GET 메소드를 사용하여 이 링크를 요청하고(전체 응답 페이지를 가져오기 위해서), class="price">를 검색하고 바로 뒤에 주식의 현재 시세가 바로 나타난다. 이것은 웹 페이지에서 재단되어 사용자에게 디스플레이 된다.

주식 시세 스크래퍼를 사용하기 위해, 관심 있는 주식 심볼을 가진 스크립트를 호출한다. (Listing 4)


Listing 4. 주식 시세 스크래퍼의 사용 예제
                [mtj@camus]$ ./stockprice.rb ibmibm current stock price 79.28 (from stockmoney.com)[mtj@camus]$ ./stockprice.rb intlintl current stock price 21.69 (from stockmoney.com)[mtj@camus]$ ./stockprice.rb ntnt current stock price 2.07 (from stockmoney.com)[mtj@camus]$




위로


예제 3: 주식 시세 스크래퍼와 통신하기

예제 2의 주식 시세용 웹 스크래퍼는 매력적이지만, 이 스크래퍼가 주식 시세를 늘 모니터링 하고, 관심 있는 주식이 오르거나 하락할 때 여러분에게 알려주도록 한다면 더욱 유용할 것이다. 기다림을 끝났다. Listing 5에서, 웹 스크래퍼를 업데이트 하여 주식을 지속적으로 모니터링 하고 주가 변동이 있을 때 이메일 메시지를 보내도록 하였다.


Listing 5. 이메일 알림을 보낼 수 있는 주식 스크래퍼
                #!/usr/local/bin/rubyrequire 'net/http'require 'net/smtp'## Given a web-site and link, return the stock price#def getStockQuote(host, link)    # Create a new HTTP connection    httpCon = Net::HTTP.new( host, 80 )    # Perform a HEAD request    resp = httpCon.get( link, nil )    stroffset = resp.body =~ /class="price">/    subset = resp.body.slice(stroffset+14, 10)    limit = subset.index('<')    return subset[0..limit-1].to_fend## Send a message (msg) to a user.# Note: assumes the SMTP server is on the same host.#def sendStockAlert( user, msg )    lmsg = [ "Subject: Stock Alert\n", "\n", msg ]    Net::SMTP.start('localhost') do |smtp|      smtp.sendmail( lmsg, "rubystockmonitor@localhost.localdomain", [user] )    endend## Our main program, checks the stock within the price band every two# minutes, emails and exits if the stock price strays from the band.## Usage: ./monitor_sp.rb <symbol> <high> <low> <email_address>#begin  host = "www.smartmoney.com"  link = "/eqsnaps/index.cfm?story=snapshot&symbol="+ARGV[0]  user = ARGV[3]  high = ARGV[1].to_f  low = ARGV[2].to_f  while 1    price = getStockQuote(host, link)    print "current price ", price, "\n"    if (price > high) || (price < low) then      if (price > high) then        msg = "Stock "+ARGV[0]+" has exceeded the price of "+high.to_s+               "\n"+host+link+"\n"      end      if (price < low) then        msg = "Stock "+ARGV[0]+" has fallen below the price of "+low.to_s+               "\n"+host+link+"\n"      end      sendStockAlert( user, msg )      exit    end    sleep 120  endend

Ruby 스크립트는 다소 길지만, Listing 3의 주식 스크래핑 스크립트를 기반으로 구현한 것이다. 새로운 함수 getStockQuote는 주식 스크래핑 함수를 캡슐화 한다. 또 다른 함수인 sendStockAlert는 메시지를 이메일 주소로 보낸다. (두 개 모두 사용자가 정의한 것이다.) 주 프로그램은 그저 반복적으로 주식 시세를 확인하고, 변동이 있는지를 체크하고, 사용자에게 이메일 알림을 보내는 것이다. 서버에 부담을 주고 싶지 않았기 때문에 주식 시세를 체크하는 사이에 딜레이를 적용했다.

Listing 6은 주식 시세 모니터링 실행 예제이다. 2분 마다 주식이 체크되고 프린트 된다. 주가가 상한선을 넘으면, 이메일 알림이 보내지고 스크립트가 종료한다.


Listing 6. 주식 모니터 스크립트 데모
                [mtj@camus]$ ./monitor_sp.rb ibm 83.00 75.00 mtj@mtjones.comcurrent price 82.06current price 82.32current price 82.75current price 83.36

결과 이메일은 그림 1과 같다. 스크립팅 된 데이터의 소스에 링크가 걸려있다.


그림1. Listing 5의 Ruby 스크립트에서 보낸 이메일 알림
Listing 5의 Ruby 스크립트에서 보낸 이메일 알림

이제 스크래퍼를 떠나서 웹 스파이더의 구조에 대해 살펴보도록 하자.




위로


예제 4: 웹 사이트 크롤러

마지막 예제에서는 웹 사이트를 크롤링 하는 웹 스파이더에 대해 설명하도록 하겠다. 보안을 위해 사이트 밖에 머무르지 않고, 대신 하나의 웹 페이지만 탐구하도록 하겠다.

웹 사이트를 크롤링 하고, 이 안에서 제공되는 링크를 따라가려면, HTML 페이지를 파싱해야 한다. 웹 페이지를 성공적으로 파싱할 수 있다면 다른 리소스에 대한 링크를 구분할 수 있다. 어떤 것은 로컬 리소스(파일)을 지정하고, 다른 것은 비 로컬 리소스(다른 웹 페이지에 대한 링크)를 나타낸다.

웹을 크롤링 하려면, 주어진 웹 페이지로 시작하여, 그 페이지에 있는 모든 링크를 파악하고, 이들을 to-visit 큐에 대기시킨 다음, to-visit 큐에서 첫 번째 아이템을 사용하여 이 프로세스를 반복한다. 이것은 breadth-first traversal(너비 우선 순회)이다. (발견된 첫 번째 링크를 통해 나아가는 것과는 대조적이다. 이것은 depth-first behavior(깊이 우선 순회)라고 한다.)

비 로컬(non-local) 링크를 피하고 로컬 웹 페이지로만 탐색한다면 웹 크롤러에게 하나의 웹 사이트를 제공한다. (Listing 7) 이 경우, 나는 Ruby에서 Python으로 전환하여 Python의 유용한 HTMLParser 클래스를 활용한다.


Listing 7. Python 웹 사이트 크롤러 (minispider.py)
                #!/usr/local/bin/pythonimport httplibimport sysimport refrom HTMLParser import HTMLParserclass miniHTMLParser( HTMLParser ):  viewedQueue = []  instQueue = []  def get_next_link( self ):    if self.instQueue == []:      return ''    else:      return self.instQueue.pop(0)  def gethtmlfile( self, site, page ):    try:      httpconn = httplib.HTTPConnection(site)      httpconn.request("GET", page)      resp = httpconn.getresponse()      resppage = resp.read()    except:      resppage = ""    return resppage  def handle_starttag( self, tag, attrs ):    if tag == 'a':      newstr = str(attrs[0][1])      if re.search('http', newstr) == None:        if re.search('mailto', newstr) == None:          if re.search('htm', newstr) != None:            if (newstr in self.viewedQueue) == False:              print "  adding", newstr              self.instQueue.append( newstr )              self.viewedQueue.append( newstr )          else:            print "  ignoring", newstr        else:          print "  ignoring", newstr      else:        print "  ignoring", newstrdef main():  if sys.argv[1] == '':    print "usage is ./minispider.py site link"    sys.exit(2)  mySpider = miniHTMLParser()  link = sys.argv[2]  while link != '':    print "\nChecking link ", link    # Get the file from the site and link    retfile = mySpider.gethtmlfile( sys.argv[1], link )    # Feed the file into the HTML parser    mySpider.feed(retfile)    # Search the retfile here    # Get the next link in level traversal order    link = mySpider.get_next_link()  mySpider.close()  print "\ndone\n"if __name__ == "__main__":  main()

이 크롤러의 기본 디자인은 첫 번째 링크를 로딩하여 큐를 검사하는 것이다. 이 큐는 next-to-interrogate 큐로서 작동한다. 링크가 체크되면, 발견된 새로운 링크들이 같은 큐에 로딩된다.

먼저, Python의 HTMLParser 클래스에서 miniHTMLParser라고 하는 새로운 클래스를 이끌어 낸다. 이 클래스는 몇 가지 일을 수행한다. 먼저, 시작 HTML 태그를 만날 때 마다 콜백 메소드(handle_starttag)를 사용하는 나의 HTML 파서이다. 나는 또한 이 클래스를 사용하여 크롤링에서 발견된 (get_next_link) 링크에 액세스 하고 이 링크에서 나타난 파일(이 경우, HTML 파일)을 가져온다.

두 개의 인스턴스 변수들이 이 클래스 안에 포함되는데, viewedQueue에는 지금까지 조사된 링크가 포함되어 있고, instQueue는 조사 될 링크들을 나타내고 있다.

여러분도 보듯, 클래스 메소드는 단순하다. get_next_link 메소드는 instQueue가 비어있는지 여부를 확인하고 리턴한다. 그렇지 않으면, 다음 아이템이 pop 메소드를 통해 리턴된다. gethtmlfile 메소드는 HTTPConnectionK를 사용하여 사이트로 연결하고 정해진 페이지의 내용을 리턴한다. 마지막으로 handle_starttag는 웹 페이지의 모든 시작 태그에 호출된다. (feed 메소드를 통해 HTML 파서로 피딩(feed) 된다.) 이 함수에서, 링크가 비 로컬 링크(http를 포함하고 있을 경우)인지 여부, 이것이 이메일 주소인지 여부(mailto), 링크에 이것이 웹 페이지라는 것을 나타내는 'htm'이 포함되었는지의 여부를 검사한다. 또한, 전에 한번도 방문한 적 없는 곳인지를 확인하고, 그렇지 않을 경우, 링크는 my interrogate에 로딩되고 큐에 나타난다.

main 메소드는 단순하다. 나는 새로운 miniHTMLParser 인스턴스를 만들고 사용자 정의 사이트(argv[1])와 링크(argv[2])로 시작한다. 링크의 콘텐트를 가져다가, 이것을 HTML 파서에 피딩하고, 다음에 방문할 링크가 있다면 그 다음 링크를 가져온다. 방문할 링크가 있는 한 루프는 계속된다.

웹 스파이더를 호출하려면, 웹 사이트 주소와 링크를 제공한다.

./minispider.py www.fsf.org /

이 경우, Free Software Foundation(자유 소프트웨어 재단)에서 루트 파일을 요청하고 있다. 이 명령어의 결과는 Listing 8과 같다. 요청 큐에 추가된 새로운 링크와 비 로컬 링크 같은 무시된 링크를 볼 수 있다. 리스팅 밑에, 루트에서 발견된 그 링크에 대한 질의를 볼 수 있다.


Listing 8. minispider 스크립트의 결과
                [mtj@camus]$ ./minispider.py www.fsf.org /Checking link  /  ignoring hiddenStructure  ignoring http://www.fsf.org  ignoring http://www.fsf.org  ignoring http://www.fsf.org/news  ignoring http://www.fsf.org/events  ignoring http://www.fsf.org/campaigns  ignoring http://www.fsf.org/resources  ignoring http://www.fsf.org/donate  ignoring http://www.fsf.org/associate  ignoring http://www.fsf.org/licensing  ignoring http://www.fsf.org/blogs  ignoring http://www.fsf.org/about  ignoring https://www.fsf.org/login_form  ignoring http://www.fsf.org/join_form  ignoring http://www.fsf.org/news/fs-award-2005.html  ignoring http://www.fsf.org/news/fsfsysadmin.html  ignoring http://www.fsf.org/news/digital-communities.html  ignoring http://www.fsf.org/news/patents-defeated.html  ignoring /news/RSS  ignoring http://www.fsf.org/news  ignoring http://www.fsf.org/blogs/rms/entry-20050802.html  ignoring http://www.fsf.org/blogs/rms/entry-20050712.html  ignoring http://www.fsf.org/blogs/rms/entry-20050601.html  ignoring http://www.fsf.org/blogs/rms/entry-20050526.html  ignoring http://www.fsf.org/blogs/rms/entry-20050513.html  ignoring http://www.fsf.org/index_html/SimpleBlogFullSearch  ignoring d0cumentContent  ignoring http://www.fsf.org/index_html/sendto_form  ignoring javascript:this.print();  adding licensing/essays/free-sw.html  ignoring /licensing/essays  ignoring http://www.gnu.org/philosophy  ignoring http://www.freesoftwaremagazine.com  ignoring donate  ignoring join_form  adding associate/index_html  ignoring http://order.fsf.org  adding donate/patron/index_html  adding campaigns/priority.html  ignoring http://r300.sf.net/  ignoring http://developer.classpath.org/mediation/OpenOffice2GCJ4  ignoring http://gcc.gnu.org/java/index.html  ignoring http://www.gnu.org/software/classpath/  ignoring http://gplflash.sourceforge.net/  ignoring campaigns  adding campaigns/broadcast-flag.html  ignoring http://www.gnu.org  ignoring /fsf/licensing  ignoring http://directory.fsf.org  ignoring http://savannah.gnu.org  ignoring mailto:webmaster@fsf.org  ignoring http://www.fsf.org/Members/root  ignoring http://www.plonesolutions.com  ignoring http://www.enfoldtechnology.com  ignoring http://blacktar.com  ignoring http://plone.org  ignoring http://www.section508.gov  ignoring http://www.w3.org/WAI/WCAG1AA-Conformance  ignoring http://validator.w3.org/check/referer  ignoring http://jigsaw.w3.org/css-validator/check/referer  ignoring http://plone.org/browsersupportChecking link  licensing/essays/free-sw.html  ignoring mailto:webmasterChecking link  associate/index_html  ignoring mailto:webmasterChecking link  donate/patron/index_html  ignoring mailto:webmasterChecking link  campaigns/priority.html  ignoring mailto:webmasterChecking link  campaigns/broadcast-flag.html  ignoring mailto:webmasterdone[mtj@camus]$

이 예제는 웹 스파이더의 크롤링 단계를 나타내고 있다. 이 파일이 클라이언트에 의해 읽혀진 후에, 페이지의 콘텐트가 검사된다.




위로


리눅스 스라이더링(spidering) 툴

두 개의 스크래퍼와 스파이더를 구현하는 방법을 배웠다. 이러한 기능을 제공하는 리눅스 툴도 있다.

Web get을 뜻하는 wget 명령어는 웹 사이트를 반복적으로 실행하고 관심 내용을 가져오는 유용한 명령어이다. 웹 사이트, 관심이 있는 내용, 기타 관리 옵션들을 지정할 수 있다. 이 명령어는 파일들을 여러분의 로컬 호스트로 가져온다. 예를 들어, 다음 명령어는 여러분이 정의한 URL로 연결하여 세 단계만 반복하여 mp3, mpg, mpeg, 또는 avi 확장자를 가진 파일을 가져온다.

wget -A mp3,mpg,mpeg,avi -r -l 3 http://<some URL>

curl 명령어도 비슷한 방법으로 작동한다. 계속해서 많은 것들이 활발히 개발되고 있다. 이와 비슷한 다른 명령어로는 snarf, fget, fetch 등이 있다.




위로


법적 문제

웹 스파이더를 사용하는 인터넷에서의 데이터 마이닝에 대한 소송들이 있었고, 잘 처리되지 않고 있다. Farechase, Inc.는 최근 American Airlines로부터 스크린 스크래핑과 관련하여 고소를 당했다. 이 소송은 American Airlines의 사용자 계약에 위반되는 데이터를 모았다는 점이 소송에 걸렸다. 소송이 실패하자, American Airlines는 불법 침해를 주장했고 이것은 성공을 거두었다. 다른 소송 건으로는 스파이더와 스크래퍼가 합법적 사용자의 대역폭을 가져가는 것과 관련한 것이었다. 모두가 근거 있는 소송들이고 Politeness 정책들을 수립하는 것이 더욱 중요해지고 있다. (참고자료)




위로


맺음말

웹의 크롤링과 스크래핑은 재미도 있고 이롭기도 하다. 하지만, 앞서 언급한 것처럼, 법적인 문제도 있다. 스파이더링이나 스크래핑을 할 때, 서버에서 사용할 수 있는 robots.txt 파일을 준수하고, 이것을 여러분의 Politeness 정책들에 추가하도록 한다. SOAP 같은 새로운 프로토콜들은 스파이더링을 더욱 쉽게 만들고, 일반 웹 작동에는 영향을 덜 준다. 시맨틱 웹 같은 노력이 스파이더링을 더욱더 단순화 하기 때문에 스파이더링의 솔루션과 방식은 계속해서 성장할 전망이다.

기사의 원문보기



참고자료

교육

제품 및 기술 얻기

토론


필자소개

M. Tim Jones는 임베디드 소프트웨어 아키텍트이며, GNU/Linux Application Programming, AI Application Programming, BSD Sockets Programming from a Multilanguage Perspective의 저자이기도 하다. 정지 우주선용 커널부터 임베디드 시스템 아키텍처와 네트워킹 프로토콜 개발까지 광범위한 개발 경험을 갖고 있다. 현재 Emulex Corp. (Longmont, Colorado)의 자문 엔지니어이다.

2008 디지털제어산업기사 자격증 사진

2008 디지털제어산업기사 최종합격!



+ Recent posts