이진 탐색은 정렬된 배열에서 원하는 value의 element를 O(logn) 이라는 좋은 성능으로 수행할 수 있는 알고리즘입니다.


문제 해결 방법은 간단합니다.


이미 정렬이 되어 있는 배열이기 때문에 배열중에 중간에 위치한 값을 체크해서 체크해야 하는 element들의 수를 반씩 줄이는 방법입니다.


import unittest
from typing import List


def binary_search(target: int, a: List[int]) -> int:
start, end = 0, len(a)
mid = (start + end) // 2
while mid != 0 and mid != len(a):
if target > a[mid]:
start = mid + 1
mid = (start + end) // 2
elif target < a[mid]:
end = mid - 1
mid = (start + end) // 2
else:
return mid

return -1


class TestCase(unittest.TestCase):
def test(self):
input_a = [i for i in range(100)]
target = 55
expected = 55

input_a2 = [i ** 2 for i in range(10)]
target2 = 81
expected2 = 9

input_a3 = [i ** 2 for i in range(10)]
target3 = -81
expected3 = -1

self.assertEqual(binary_search(target, input_a), expected)
self.assertEqual(binary_search(target2, input_a2), expected2)
self.assertEqual(binary_search(target3, input_a3), expected3)


코드를 보시면 보는 것 과 같이 가운데 값을 체크해서 원하는 값보다 크면 오른 쪽을 버리고,


작으면 왼쪽을 버리고 길이를 반으로 줄인 배열을 다시 같은 방법으로 줄여가면서 원하는 값을 찾습니다.


코드는 github으로도 확인하실수 있습니다.


https://github.com/baidoosik/ProblemSolving/blob/master/Search/binary_search.py



감사합니다.








이진 트리 구현

이진 트리는 부모 노드가 left, right 자식 노드를 가지는 나무를 거꾸로 한 모양의 자료구조

* search, add, delete가 O(logn)의 성능으로 좋은 성능을 가진 자료구조이기 때문에 여러가지 프로그램을 만드는데 자주 쓰입니다.

* preorder_traverse 는 말 그대로 root 노드를 먼저 순회하기 때문에 preorder traverse이고, root노드 -> left 노드 -> right 노드 순으로 순회하고, 전송시에 이진 트리의 모형을 그대로 유지하기 때문에 전송시에 사용됩니다.

* inorder_traverse 도 마찬가지로 root 노드를 중간에 순회해서 inorder traverse입니다. left 노드 -> root 노드 -> right 노드 순으로 순회하고, 오름차순으로 정렬된 값이 나오게 됩니다.

* postorder_traverse 는 root 노드를 가장 나중에 순회하는 방법으로 left 노드 -> right 노드 -> root 노드 순으로 순회합니다.





구현 코드 및 테스트 코드

import unittest


class Node:
def __init__(self, value):
self.value = value
self.right = None
self.left = None


class BinaryTree:
def __init__(self):
self.head = Node(None)

# test purpose lists
self.preorder_list = []
self.inorder_list = []
self.postorder_list = []

def add(self, value):
if self.head.value is None:
self.head.value = value
else:
self._add(self.head, value)

def _add(self, cur, value):
if cur.value < value:
if cur.right is None:
cur.right = Node(value)
else:
self._add(cur.right, value)
else:
if cur.left is None:
cur.left = Node(value)
else:
self._add(cur.left, value)

def search(self, value):
if self.head.value is None:
return False
else:
return self._search(self.head, value)

def _search(self, cur, value):
if cur.value == value:
return True
else:
if cur.value < value:
if cur.right is None:
return False
else:
return self._search(cur.right, value)
else:
if cur.left is None:
return False
else:
return self._search(cur.left, value)

def remove(self, value):
if self.head.value is None:
return print("there is no value in this binary tree")
else:
if self.head.value == value:
if self.head.left is None and self.head.right is None:
self.head = Node(None)
elif self.head.left is None and self.head.right is not None:
self.head = self.head.right
elif self.head.left is not None and self.head.right is None:
self.head = self.head.left
else:
self.head.value = self._most_left_val_from_right_node(self.head.right).value
self._remove_item(self.head, self.head.right)
return True
else:
if self.head.value < value:
self._remove(self.head, self.head.right, value)
else:
self._remove(self.head, self.head.left, value)

def _remove(self, prev, cur, value):
if cur is None:
return print("not found value")

if cur.value < value:
self._remove(cur, cur.right, value)
elif cur.value > value:
self._remove(cur, cur.left, value)
else:
if cur.left is None and cur.right is None:
if prev.left.value == value:
prev.left = None
else:
prev.right = None
elif cur.left is None and cur.right is not None:
if prev.left.value == value:
prev.left = cur.right
else:
prev.right = cur.right
elif cur.left is not None and cur.right is None:
if prev.left.value == value:
prev.left = cur.left
else:
prev.right = cur.left
else:
cur.value = self._most_left_val_from_right_node(cur.right).value
self._remove_item(cur, cur.right)
return True

def _most_left_val_from_right_node(self, cur):
if cur.left is not None:
return self._most_left_val_from_right_node(cur.left)
else:
return cur

def _remove_item(self, prev, cur):
if cur.left is None:
prev.right = cur.right
else:
while cur.left is not None:
prev = cur
cur = cur.left
if cur.right is not None:
prev.left = cur.right
else:
prev.left = None

def preorder_traverse(self):
if self.head is not None:
self._preorder_traverse(self.head)

def _preorder_traverse(self, cur):
self.preorder_list.append(cur.value)
if cur.left is not None:
self._preorder_traverse(cur.left)
if cur.right is not None:
self._preorder_traverse(cur.right)

def inorder_traverse(self):
if self.head is not None:
self._inorder_traverse(self.head)

def _inorder_traverse(self, cur):
if cur.left is not None:
self._inorder_traverse(cur.left)

self.inorder_list.append(cur.value)

if cur.right is not None:
self._inorder_traverse(cur.right)

def postorder_traverse(self):
if self.head is not None:
self._postorder_traverse(self.head)

def _postorder_traverse(self, cur):
if cur.left is not None:
self._postorder_traverse(cur.left)
if cur.right is not None:
self._postorder_traverse(cur.right)
self.postorder_list.append(cur.value)


class BinaryTreeTest(unittest.TestCase):
def test(self):
bt = BinaryTree()
bt.add(6)
bt.add(3)
bt.add(4)
bt.add(1)
bt.add(7)
print("pre order")
bt.preorder_traverse()
self.assertEqual(bt.preorder_list, [6, 3, 1, 4, 7])

print("in order")
bt.inorder_traverse()
self.assertEqual(bt.inorder_list, [1, 3, 4, 6, 7])

print("post order")
bt.postorder_traverse()
self.assertEqual(bt.postorder_list, [1, 4, 3, 7, 6])

print("bt root remove")
bt_root_remove_test = BinaryTree()
bt_root_remove_test.add(60)
bt_root_remove_test.add(50)
bt_root_remove_test.add(70)
bt_root_remove_test.remove(60)
bt_root_remove_test.preorder_traverse()
self.assertEqual(bt_root_remove_test.preorder_list, [70, 50])

print("bt root remove2")
bt_root_remove_test = BinaryTree()
bt_root_remove_test.add(60)
bt_root_remove_test.add(50)
bt_root_remove_test.remove(60)
bt_root_remove_test.preorder_traverse()
self.assertEqual(bt_root_remove_test.preorder_list, [50])

print("bt root remove3")
bt_root_remove_test = BinaryTree()
bt_root_remove_test.add(60)
bt_root_remove_test.add(70)
bt_root_remove_test.remove(60)
bt_root_remove_test.preorder_traverse()
self.assertEqual(bt_root_remove_test.preorder_list, [70])

print("bt left remove 1")
bt_left_remove_test_1 = BinaryTree()
bt_left_remove_test_1.add(60)
bt_left_remove_test_1.add(50)
bt_left_remove_test_1.add(70)
bt_left_remove_test_1.remove(50)
bt_left_remove_test_1.preorder_traverse()
self.assertEqual(bt_left_remove_test_1.preorder_list, [60, 70])

print("bt left remove 2")
bt_left_remove_test_2_left = BinaryTree()
bt_left_remove_test_2_left.add(60)
bt_left_remove_test_2_left.add(50)
bt_left_remove_test_2_left.add(70)
bt_left_remove_test_2_left.add(40)
bt_left_remove_test_2_left.remove(50)
bt_left_remove_test_2_left.preorder_traverse()
self.assertEqual(bt_left_remove_test_2_left.preorder_list, [60, 40, 70])

print("bt right remove 2")
bt_left_remove_test_2_right = BinaryTree()
bt_left_remove_test_2_right.add(60)
bt_left_remove_test_2_right.add(50)
bt_left_remove_test_2_right.add(70)
bt_left_remove_test_2_right.add(55)
bt_left_remove_test_2_right.remove(50)
bt_left_remove_test_2_right.preorder_traverse()
self.assertEqual(bt_left_remove_test_2_right.preorder_list, [60, 55, 70])

print("bt right remove 1")
bt_right_remove_test_1 = BinaryTree()
bt_right_remove_test_1.add(60)
bt_right_remove_test_1.add(50)
bt_right_remove_test_1.add(70)
bt_right_remove_test_1.remove(70)
bt_right_remove_test_1.preorder_traverse()
self.assertEqual(bt_right_remove_test_1.preorder_list, [60, 50])

print("bt right remove 2")
bt_right_remove_test_2_left = BinaryTree()
bt_right_remove_test_2_left.add(60)
bt_right_remove_test_2_left.add(50)
bt_right_remove_test_2_left.add(70)
bt_right_remove_test_2_left.add(65)
bt_right_remove_test_2_left.remove(70)
bt_right_remove_test_2_left.preorder_traverse()
self.assertEqual(bt_right_remove_test_2_left.preorder_list, [60, 50, 65])

print("bt right remove 3")
bt_right_remove_test_1 = BinaryTree()
bt_right_remove_test_1.add(60)
bt_right_remove_test_1.add(78)
bt_right_remove_test_1.add(70)
bt_right_remove_test_1.add(50)
bt_right_remove_test_1.add(55)
bt_right_remove_test_1.add(65)
bt_right_remove_test_1.add(67)
bt_right_remove_test_1.add(69)
bt_right_remove_test_1.add(79)
bt_right_remove_test_1.remove(70)
bt_right_remove_test_1.preorder_traverse()
self.assertEqual(bt_right_remove_test_1.preorder_list, [60, 50, 55, 78, 65, 67, 69, 79])

print("bt right remove 2")
bt_right_remove_test_2_right = BinaryTree()
bt_right_remove_test_2_right.add(60)
bt_right_remove_test_2_right.add(50)
bt_right_remove_test_2_right.add(70)
bt_right_remove_test_2_right.add(75)
bt_right_remove_test_2_right.remove(70)
bt_right_remove_test_2_right.preorder_traverse()
self.assertEqual(bt_right_remove_test_2_right.preorder_list, [60, 50, 75])

print("bt left remove 3")
bt_left_remove_test_3 = BinaryTree()
bt_left_remove_test_3.add(60)
bt_left_remove_test_3.add(50)
bt_left_remove_test_3.add(70)
bt_left_remove_test_3.add(40)
bt_left_remove_test_3.add(55)
bt_left_remove_test_3.add(52)
bt_left_remove_test_3.remove(50)
bt_left_remove_test_3.preorder_traverse()
self.assertEqual(bt_left_remove_test_3.preorder_list, [60, 52, 40, 55, 70])

print("BST search test")
bt_search_test = BinaryTree()
bt_search_test.add(60)
bt_search_test.add(50)
bt_search_test.add(70)
bt_search_test.add(40)
bt_search_test.add(55)
bt_search_test.add(52)
self.assertTrue(bt_search_test.search(60))
self.assertTrue(bt_search_test.search(50))
self.assertTrue(bt_search_test.search(70))
self.assertTrue(bt_search_test.search(40))
self.assertTrue(bt_search_test.search(55))
self.assertTrue(bt_search_test.search(52))
self.assertFalse(bt_search_test.search(61))
self.assertFalse(bt_search_test.search(81))


코드가 조금 많이 긴 편입니다. 도움이 되셨으면 좋겠습니다.

github에서도 쉽게 코드를 확인하실 수 있습니다.


https://github.com/baidoosik/ProblemSolving/blob/master/DataStructure/BinaryTree.py


감사합니다.










C# 에서 

어떤 변수를 기존에 만든 객체로 할당하게 되면 해당 변수는 그냥 기존에 만들어진 객체를 참조하기 때문에 기존에 있던 객체와 같은 객체가 된다.


예시를 보면 쉽게 이해할 수 있다.


Ex)

public class Car
{
    public string Name;

    public string Color;
}

Car car1 = new Car{Name=“supercar”, Color=“red”};

Car car2 = car1;

car1.Name = “my supercar”;

라고 car1의 Name 을 변경했을 때

Console.WriteLine(car2.Name);

을 출력하면 my supercar 가 출력된다.

즉, car1과 car2는 같은 메모리 어드레스를 참조하는 변수인 것이다.


그렇다면 car1과 같은 value를 같는 새로운 객체를 만들고 싶다면 어떻게 해야할까?

Copy 와 관련된 메서드인 닷넷 프레임워크에서 제공해주는 Object 클래스의 MemberwiseClone 메서드를 이용해서 ShallowCopy() 메서드와 DeepCopy() 메서드를 만들어서 사용하면된다.

두 메서드 의 차이는 클래스가 참조형 멤버를 가지고 있을 때 참조형 멤버 객체를 새로 생성하는냐 생성하지 않느냐에 차이다.


일단 참조형 멤버를 가진 클래스를 만들어보자.

public class CarBrand
{
    public string Name;

    public string PhoneNum;
}

public class Car
{
    public string Name;

    public string Color;

    public CarBrand Brand; 

    public Car ShallowCopy()
    {
        return (Car)this.MemberwiseClone();
    {

    public Car DeepCopy()
    {
        Car other = (Car) this.MemberwiseClone();
        other.Brand = new CarBrand(Brand);
        other.Name = String.Copy(Name);
        other.Color = String.Copy(Color);
        return other;
    }
}

Car 라는 객체는 CarBrand라는 객체를 멤버로가지고 있다.


이제 우리가 만든 ShallowCopy 와 DeepCopy()를 예제 코드를 사용해서 비교해보자.

EX)
CarBrand carBrand = new CarBrand{Name=“Audi”, PhoneNum=“01012341234”};
Car car1 = new Car {Name=“a4”, Color=“white”, Brand=carBrand};

Car car2 = car1.ShallowCopy();
car2.Name = “sonata”;
car2.Brand = new CarBrand{Name=“Hyundai”, PhoneNum=“01012341234”};

로 하고 값을 확인해보면

car1의 Brand로 현대로 바뀐 것을 확인할 수 있다. Name 은 여전히 a4이다.

즉, Brand 객체를 같이 참조하고 있는 것을 확인 할 수 있다.



포함하고 있는 참조 객체까지 새롭게 만들고 싶다면 위에서 만든 DeepCopy() 함수를 이용하면 된다.

EX)
CarBrand carBrand = new CarBrand{Name=“Audi”, PhoneNum=“01012341234”};
Car car1 = new Car {Name=“a4”, Color=“white”, Brand=carBrand};

Car car2 = car1.ShallowCopy();
car2.Name = “sonata”;
car2.Brand = new CarBrand{Name=“Hyundai”, PhoneNum=“01012341234”};

로 하고 값을 확인해보면

car1의 Brand는 여전히 audi이고 car2의 Brand만 현대로 바뀐 것을 확인할 수 있다. 

즉, car2 에서 Brand 객체를 새롭게 만들어서 사용한것 을 확인할 수 있다.



위에서 살펴본 것 처럼 기본적으로 만들어진 객체를 이용해서 변수에 할당하게 되면 새로운 객체가 아니라 기존에 객체를 참조하게 되므로 shallowcopy나 deepcopy 같은 함수를 만들어서 사용해야 한다. 

shallowcopy와 deepcopy의 차이는 가지고 있는 멤버 중에 참조객체를 
새로 만드느냐 아니면 기존의 것을 같이 참조하냐의 차이다.







C# 과 ASP.NET CORE를 공부하면서 가장 힘든 점 중 하나는 턱없는 한글 문서입니다.

그래서 비동기 함수에서 ConfigureAwait(false)를 왜 사용하는지에 대한 좋은 글이 있어서 번역하게 됐습니다.

저자의 동의를 얻어서 번역한 자료입니다!



다른 분들에게 도움이 되었으면 좋겠네요 :)



원본:

저자: Juan


본문:

.NET4.5  부터 async/await 를 도입하면서 asynchronous code를 작성하기가 많이 쉬워졌다.

Async/Await 키워드들은 synchronous 코드 와 비슷하고 컴파일러가 asynchrous 프로그래밍에서 처리하기 가장 어려운 부분을 처리해주면서코드 가독성과 프로그래머의 생산성을 향상시켰다.

Async 코드를 만들기가 얼마나 쉬운지 알아보기 위해 컨텐츠를 string으로 반환하는 curl 코드를 example로 만들어보자.

public async Task<string> DoCurlAsync()
{
    using(var httpClient = new HttpClient())
    using(var httpresponse = await httpclient.GetAsync(“https://www.bynder.com”))
    {
        return await httpResponse.Content.ReadAsStringAsync();
    }
}


우리는 bynder의 content를 가져오는 동안 다른 쓰레드를 호출하는 것을 막지 않는 비동기 호출을 했다.

이론적으로(상상 속에서) 사람들은 항상 우리가 만든 DoCurlAsync 함수를 아래와 같이 사용할 것 이다.

var bynderContnets = await DoCurlAsync();


하지만, 프로그래밍 세계는 이상과 거리가 멀다. 몇몇 사람들은 다음과 같이 사용할 수 있다.

var bynderContents = DoCurlAsync().Result

이렇게 코드를 작성하면 동기 방식으로 코드가 수행되기 때문에 curl 함수가 종료될 때 까지 다른 쓰레드를 호출 하는것을 막는다. 만약 콘솔 어플리케이션을 실행시키는 중이라면, 우리의 코드는 대부분 예상대로 실행될 것이다.

그러나 아래와같이, 만약 UI Application에서 수행이 되어진다면, 예를 들어서 button을 클릭했을 때 수행이 되는거라면

public void OnButtonClicked(object sender, RouteEventArgs e)
{
    var bynderContents = DoCurlAsync().Result;
}


이 어플리케이션은 동작하지 않을 것이고 deadlock 상태가 되어 버립니다. 물론, 우리가 만든 함수를 사용하는 사람들은 우리의 함수가 application을 응답하지 못하게 만들었다고 불평할 것 입니다.

이와 같은 상황의 문제를 해결하기 위해서 우리는 함수를 아래와 같이 다시 작성합니다.

public async Task<string> DoCurlAsync()
{
    using(var httpClient = new HttpClient())
    using(var httpresponse = await httpclient.GetAsync(“https://www.bynder.com”).ConfigureAwait(false))
    {
        return await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
    }


사실 , 첫번째의 비동기 구문에 ConfigureAwait(false) 를 붙이는 것으로 이 문제는 충분히 해결할 수 있습니다.

public async Task<string> DoCurlAsync()
{
    using(var httpClient = new HttpClient())
    using(var httpresponse = await httpclient.GetAsync(“https://www.bynder.com”).ConfigureAwait(false))
    {
        return await httpResponse.Content.ReadAsStringAsync();
    }

결론적으로, 항상 ConfigureAwait(false)를 사용하는 것은 우리가 원치 않은 상황을 막기위한 good practice 입니다.




이제, 우리는 UI 어플리케이션(대부분의 콘솔 어플리케이션이 아닌)에서 dead lock이 발생하는지 분석하고 왜 ConfigureAwait(false)과 이 문제를 해결하는지 분석해볼 것 입니다.


먼저, 우리는 UI 어플리케이션 어떻게 동작하는지 이해하는 작업이 필요합니다.

  • UI 응답을 위한 UI 스레드라는 하나의 스레드가 있습니다. UI는 오직 이 쓰레드를 호출해야만 업데이트 할 수 있습니다. 그래서 만약 에 쓰레드 호출이 막혀 있다면 어플리케이션은 응답하지 않은 것으로 보입니다.
  • UI 쓰레드는 수행할 notification과 action을 받을 message queue를 가지고 있습니다. Win32에서는 아래와 같이 표현할 수 있습니다.

    • while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
      {
          // No errors are handled, for simplicity purposes.
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }

  • UI 쓰레드는 기본적으로 SynchronizationContext를 가지고 있습니다.
    • SynchronizationContext
      다양한 동기화 모델에서 동기화 컨텍스트를 전파하는 기본 기능을 제공해주는 class

만약에 SynchronizationContext가 있다면 (UI 스레드에서 코드가 실행된다면) await 이후의 코드들은 원래의 thread context에서 수행될 것 라고 기본적으로 예상할 수 있습니다.

만약, 아래 예제와 같이 우리가 UI 컴포넌트를 UI 스레드가 아닌 다른 스레드에서 수정하려고 한다면 System.InvalidOperationException이 발생할 것입니다.


public void OnButtonClicked(object sender, RouteEventArgs e)
{
    var bynderContents = await DoCurlAsync();
    myTextBlock.text = bynderContents;
}


다시 우리의 예제 코드를 돌아와보면 , 우리의 async DoCurlAsync 호출은 개념적으로 아래 코드와 같다.

var currentContext = SynchronizationContext.Current;
var httpResponseTask = httpClient.GetAsync("https://www.bynder.com");
httpResponseTask.ContinueWith(delegate
{
    if (currentContext == null)
    {
        return await httpResonse.Content.ReadAsStringAsync();
    }   
    else
    {
        currentContext.Post(delegate {
            await httpResonse.Content.ReadAsStringAsync();
        }, null);
     }
}, TaskScheduler.Current);


NOTE: 이 snippet은 await 구문을 사용했을 때 발생하는 것과 비슷한 버젼의 코드다. 이것은 resources들은 사용하고 closing하는 부분은 다루지 않았다.

Post 요청은 UI 스레드 메시지 펌프에 처리 될 메시지들을 보내고, 그래서 DoCurlAsync를 끝내기 위해서는 UI Thread가  await httpResonse.Content.ReadAsStringAsync();를 실행하는 것이 필수적이다.

그러나 , 아래와 같은 시나리오에서는

public void OnButtonClicked(object sender, RoutedEventArgs e)
{
    var bynderContents = DoCurlAsync().Result;
}

UI 스레드가 막혀 있으므로 instruction을 수행할 수없다. DoCurlAsync는 결코 끝나지 않으므로 dead lock상태에 빠진다. ConfigureAwait(false)는 await 후의 코드를 호출자 컨텍스트에서 수행할 필요가 없게 해준다. 그러므로 어떠한 deadlock도 피할 수 있다. 



이 글은 왜 비동기 함수를 호출 할때 ConfigureAwait(fasle)를 호출하는 것이 

Best Practice 인지 설명해줍니다.


그 이유가 궁금하셨던 분들이 계셨다면 도움이 되면 좋겠습니다.


감사합니다.








쿠키와 세션은 웹 클라이언트와 서버를 개발하는 분들 뿐만 아니라 대부분의 개발자분들은 많이 들어본 단어일 거라고 생각합니다.


한 번 정리하고 넘어가야 할 필요가 있는 것 같아서 여러자료를 참고하여 정리해봤습니다.



참고 자료는 맨 아래 기재하였습니다.



그럼 왜 쿠키와 세션이 필요한지 부터 살펴보겠습니다.

HTTP 는 connectionless , stateless 한 특징을 가지고 있습니다.

풀어서 이야기를 해보면

  • Connectionless: 연결되어 있지 않은 상
    • HTTP 통신 프로토콜은 socket 통신과는 달리 연결을 유지하고 있지 않습니다.
    • client는 server에 HTTP request하고 server는 HTTP response를 하고 연결은 끊어집니다.

  • Stateless :state(상태)를 가지고 있지 않음. Server는 client 와 통신이 끝난후 상태 정보를 가지고 있지않습니다.


위와 같은 특징으로 인해 Client는 매번 상태를 처음부터 다시 서버에게 알려야 하는 상황이 된다면 

구현하는 측면에서나 성능 측면에서 리소스를 낭비하게 된다는 문제가 발생합니다.


HTTP 에서는 이러한 문제를 해결할 방법으로 제시된 방법이 쿠기와 세션입니다.


먼저, 쿠키는 

쿠키란?(from 위키백과)

쿠키(cookie)란 하이퍼 텍스트의 기록서(HTTP)의 일종으로서 인터넷 사용자가 어떠한 웹사이트를 방문할 경우 그 사이트가 사용하고 있는 서버를통해 인터넷 사용자의 컴퓨터에 설치되는 작은 기록 정보 파일을 일컫는다. HTTP 쿠키, 웹 쿠키, 브라우저 쿠키라고도 한다.

여기서 주목해야 할 부분은 '사용자의 컴퓨터에 설치되는 작은 기록 정보 파일’ 입니다.

위의 말은 데이터의 저장위치를 나타내는데 이것이 쿠키와 세션의 가장 큰 차이 중 하나입니다. (세션은 서버에 정보를 저장) 자세한 비교는 세션까지 살펴보고 비교해 보도록 하겠습니다.


쿠키의 동작 원리

1) 클라이언트가 웹 브라우저의 도메인에 접속

2) 클라이언트가 요청한 웹페이지를 response를 받으면서 쿠키(클라이언트의 상태 정보를 가진 작은 파일)가 사용자의 하드에 저장

3)클라이언트가 해당 도메인에 재방문시 웹 페이지 요청과 함께 쿠키도 함께 전달.


쿠키 구현 예제

header에 Set-Cookie 헤더를 포함시킨다.

cookie는 key-value 쌍의 데이터를 가지고 있다.

Ex ) Set-Cookie: session-id=123645&name=“Jordan”; max-age:86400; 

위와 같이 쿠키를 클라이언트에 response하고, client에서 request시 쿠키를 보내준다면(보내주지 않을 수도 있음) 쿠키 데이터를 통해서 client의 상태를 유지할 수 있다.


쿠키 사용의 예시(무조건 쿠키를 사용했다는 것은 아니고, 쿠키를 이용해서 구현이 가능)

  • 이벤트 팝업 창 (오늘 다시 보지 않음)
  • 쇼핑몰의 장바구니 기능

쿠키 단점
  • 사용자의 상태 데이터가 로컬에 저장됨으로 조작될 가능성이 있음.
  • 도메인 당 저장할 수 있는 쿠키의 양이 정해져 있음.



그 다음으로 세션을 살펴보겠습니다.


(Http) 세션이란?

서버에서 웹 클라이언트에 ID를 발급해주고 서버에 해당 ID 에 대한 상태를 저장하여서 client의 상태를 유지하는 방법

세션의 동작 원리

- 서버에서 세션 ID를 클라이언트가 해당 웹 사이트에 접속 시 발급.
- 서버에서 http response의 쿠키를 사용해서 client에 전달.
- 클라이언트에서 다시 요청을 할 때 세션 id 값을 전달하면, 서버에서 id를 통해 client의 상태 정보를 가져옴

세션의 장점

- 각 클라이언트에게 고유한 id를 발급하여 구분 할 수 있음. (쿠키의 경우 데이터를 로컬에서 가지고 있기 때문에 구분할 수 없음)
- 각 클라이언트의 요구에 맞게 서비스를 제공
- 보안 측면에서 쿠키보다 우수.

세션의 단점

- 서버에서 session-id 별 상태를 저장해야하기 때문에 해당 id 를 받아서 처리해야하는 리소스와 저장하는 저장 공간 리소스가 필요하게 되서 서버에 무리가 될 수 있음.
- 브라우저를 종료하면 session-id 가 사라질 수 있음 (세션 토큰으로 다시 재발급 받을 수도 있음.)

세션 사용의 예

- 로그인과 같은 보안과 관련된 작업은 세션을 통해 구현.




이제 마지막으로 쿠키와 세션을 비교해도록 하겠습니다.


쿠키와 세션의 비교

가장 큰 차이점은 상태 정보를 저장하는 공간의 위치다. 쿠키는 사용자의 로컬에 세션은 서버에 저장되기 때문에 이것으로 파생되는 장단점이 있다. 또, 세션 같은 경우는 브라우저가 종료되면 라이프 싸이클이 종료 될 수 있지만 요즘은 세션 토큰을 쿠키(로컬에)에 저장해서 session-id를 재발급 받는다.


출처: 제타위키 


추가적으로 내가 자주사용하는 프레임워크들이 제공하는 authentication에 대해서 살펴봐야겠다.

시간이 되는데로 정리해서 블로그에 기록해 보도록 해야겠다!


참고 자료:

블로그

위키백과

제타위키












URL rewrite (url 재작성) VS URL redirection


Url redirection 작업은 client 작업으로 서버를 왕복해야한다.



Url rewrite 작업은 서번 내에서 특정 url request 왔을 url 다시 작성해서 서버에 요청함으로써 client에서 서버를 왕복할 필요가 없다.


URL 재작성 미들웨어를 사용해야 하는 경우

Windows Server에서 IIS URL 재작성 모듈 사용할 없거나, Apache Server에서 Apache mod_rewrite 모듈 사용할 없거나, Nginx에서 URL 재작성 사용할 없거나, 또는 응용 프로그램이 HTTP.sys 서버(기존의 WebListener)에서 호스팅 되는 경우에 URL 재작성 미들웨어를 사용해야 합니다. IIS, Apache 또는 Nginx에서 서버 기반의 URL 재작성 기술을 사용하는 가장 이유는 미들웨어가 이런 모듈들의 모든 기능을 지원하지 않고 대부분 미들웨어의 성능이 모듈의 성능을 따라가지 못하기 때문입니다. 그러나 IIS 재작성 모듈의 IsFile  IsDirectory 제약 조건 같이 ASP.NET Core 프로젝트에서 동작하지 않는 일부 서버 모듈 기능도 존재합니다. 바로 이런 시나리오에서 대신 미들웨어를 사용합니다.

=> 최대한 단에서 URL 재작성을 하는 좋다. (성능상)

이용
    using (StreamReader apacheModRewriteStreamReader =
        File.OpenText("ApacheModRewrite.txt"))
    using (StreamReader iisUrlRewriteStreamReader =
        File.OpenText("IISUrlRewrite.xml"))
    {
// 처리하고자 하는 순서대로 여러 규칙을 연결하면 됩니다
        var options = new RewriteOptions()
            .AddRedirect("redirect-rule/(.*)", "redirected/$1")
            .AddRewrite(@"^rewrite-rule/(\d+)/(\d+)", "rewritten?var1=$1&var2=$2",
                skipRemainingRules: true)
            .AddApacheModRewrite(apacheModRewriteStreamReader)
            .AddIISUrlRewrite(iisUrlRewriteStreamReader)
            .Add(MethodRules.RedirectXMLRequests)
            .Add(new RedirectImageRequests(".png", "/png-images"))
            .Add(new RedirectImageRequests(".jpg", "/jpg-images"));
        app.UseRewriter(options);
    }

RedirectOption 인스턴스를 만들어주고 UserRewriter 인자로 넣어준다.


Redirect
 .AddRedirect("redirect-rule/(.*)", "redirected/$1") 사용하면 redirect 사용할 있고, 기본적으로 302 status code 응답한다.


301 vs 302 , 검색 엔진 최적화 관점

301 라디이렉트는 무엇인가요?
301 혹은 영구이동(Permanently Moved) 해당 URL 영구적으로 새로운 URL 변경되었음을 나타냅니다. 검색엔진 크롤러는 301 요청을 만나면 컨텐트가 완전히 새로운 URL 영원히 이동했다고 판단합니다.

301 언제써야 할까요?
예를들어 웹사이트의 도메인을 변경했거나 새로운 URL 구조로 개편했을 사용할 있습니다. 검색엔진은 301 요청을 만나면 컨텐트가 새로운 URL 영원히 이동했다고 판단합니다. 따라서 검색엔진은 과거 URL 페이지랭킹과 평가점수를 새로운 URL 이동시킵니다.

302 리다이렉트는 무엇인가요?
302 리다이렉트의 의미는 요청한 리소스가 임시적으로 새로운 URL 이동했음(Temporarily Moved) 나타냅니다. 따라서 검색엔진은 페이지랭킹이나 링크에 대한 점수를 새로운 URL 옮기지 않으며 기존 URL 그대로 유지합니다. 검색엔진이 기존 URL 보유한 페이지랭킹 점수는 그대로 유지하도록 하면서 컨텐트만 새로운 URL에서 조회하도록 해야할 유용합니다

302 언제써야 할까요?
예를들어 쇼핑몰과 같은 전자상거래 사이트를 운영한다고 생각해봅시다. 인기리에 팔리는 제품이 일시적으로 재고가 떨어지거나 혹은 특정한 계절이나 기간에만 한정적으로 파는 제품이였다고 가정해봅시다. 해당 제품이 보유한 사이트랭크를 유지하면서 사용자에게 일시적으로 제품이 품절됐음을 알리려면 어떻게 해야할까요? 이럴 301 사용하거나 혹은 페이지의 컨텐트를 변경하게 되면 사이트랭킹 점수가 달라지게 것입니다. 대신 302 사용하게 되면 검색엔진은 일시적으로 해당 URL 사이트랭크는 보존하게 되며 사용자는 새로운 URL 컨텐트를 보게 됩니다.

출처http://nsinc.tistory.com/168 [NakedStrength Inc.]


AddRedirectToHttps
AddRedirectToHttpsPermanent